95
Reladomo in Scala 株式会社FOLIO 伊藤博志 グッドフロー・テクノロジーズ 瀬良和弘 Scala関西 Summit 2017.9.9 Reladomo is an open source software Licensed under Apache 2.0 License, Copyright 2016 Goldman Sachs, Its name may be a trademark of its owner. X

Reladomo in Scala #scala_ks

Embed Size (px)

Citation preview

Page 1: Reladomo in Scala #scala_ks

Reladomo in Scala

株式会社FOLIO 伊藤博志

グッドフロー・テクノロジーズ 瀬良和弘

Scala関西 Summit 2017.9.9

Reladomo is an open source software Licensed under Apache 2.0 License,Copyright 2016 Goldman Sachs, Its name may be a trademark of its owner.

X

Page 2: Reladomo in Scala #scala_ks

1

Agenda

1. 自己紹介

2. 株式会社FOLIOでの開発とReladomo

3. scala-reladomoの紹介

4. バイテンポラルモデル

5. scala-reladomoの… OSS公開!!!!

hashtag: #scala_ks_main

Page 3: Reladomo in Scala #scala_ks

2

自己紹介

Head of Engineering @ FOLIO

伊藤 博志

2015年末からEclipse Collections共同プロジェクトリード兼コミッターをしています。Reladomo/OpenJDKにもちょっとだけコントリビュートしたりしています。2017年、Scalaはじめました。JavaOne、Java Day Tokyo、JJUG CCC 登壇

瀬良 和弘Goodflow Technologies

ScalikeJDBC や Skinny Framework などの Scala OSS を開発しています。グッドフロー・テクノロジーズ (http://good-flow.com/)の屋号で、技術支援を承ったり、Scala の普及活動も行っています。

hashtag: #scala_ks_main

Page 4: Reladomo in Scala #scala_ks

3

Reladomoとはなにか

hashtag: #scala_ks_main

Page 5: Reladomo in Scala #scala_ks

4

を説明する前に

hashtag: #scala_ks_main

Page 6: Reladomo in Scala #scala_ks

5

Reladomoに解決してほしい問題

の例を挙げてみます

hashtag: #scala_ks_main

Page 7: Reladomo in Scala #scala_ks

6

株式会社FOLIOにおける開発

https://folio-sec.com/

hashtag: #scala_ks_main

Page 8: Reladomo in Scala #scala_ks

7

証券システム開発の難しさ

hashtag: #scala_ks_main

Page 9: Reladomo in Scala #scala_ks

8

いきなり難しい例を出します

hashtag: #scala_ks_main

Page 10: Reladomo in Scala #scala_ks

9

たとえば株式分割等のコーポレートアクションの表現

1株A社1株10,000円

1株1株1株A社 1株2,000円

1株 5株

X月Y日コーポレートアクション

1:5の株式分割X月Y日を境に起きる

Page 11: Reladomo in Scala #scala_ks

10

たとえば株式分割等のコーポレートアクションの表現

1:5の株式分割システム上のお客様の保有株数の変化ある特定の日時X月Y日 S時T分に反映される

A社 10株 A社 50株

X月Y日 S時T分バッチジョブによる株数変更

Page 12: Reladomo in Scala #scala_ks

11

たとえば株式分割等のコーポレートアクションの表現

A社株10,000円

A社株2,000円

X月Y日 A時B分DBに反映

1:5の株式分割データソースベンダーから受け取る株価の変化ある特定の日時X月Y日 A時B分に反映される

コーポレートアクション

Page 13: Reladomo in Scala #scala_ks

12

たとえば株式分割等のコーポレートアクションの表現

システム反映のタイミングの違いで起きうる計算結果のズレにどう対処すれば良いのか?

10株 50株

10,000円 2,000円

保有株数

株価

資産評価額

🤔

10株 x 10,000円= 100,000円

50株 x 2,000円= 100,000円

50株

x 10,000円

= 500,000円

X月Y日 S時T分 X月Y日 A時B分

Page 14: Reladomo in Scala #scala_ks

13

RDBMSで履歴・有効期間データを扱う

hashtag: #scala_ks_main

Page 15: Reladomo in Scala #scala_ks

14

RDBMSで履歴データを扱う

単純なシナリオを考えてみましょう

シンプルな人事システム。扱う情報は姓名のみ。

4月1日に「鈴木花子」さん入社。3月1日にシステムに登録

6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録

6月8日にシステム上で修正(斉藤=>斎藤)。事実としては6月6日づけで直したい

12月1日、退職にともない同日にシステム上で情報を無効化

hashtag: #scala_ks_main

Page 16: Reladomo in Scala #scala_ks

15

RDBMSで履歴データを扱う

シナリオをよく観察すると、2種類の履歴が存在

2種類の時間表現なのでbi-temporal:バイテンポラル

3/1 4/1 6/6 6/8 12/1

鈴木花子

事実情報の履歴

システムに反映された

履歴

入社🎉

「鈴木花子」をシステムに登録

結婚して斎藤になる👰

名字を「斉藤」に変更

名字を「斎藤」に変更

退職†

データを無効化

このような、履歴や有効期間情報を扱えるデータモデルを

テンポラルデータモデル、上記のような2種類の時間表現が可能なモデルをバイテンポラルデータモデルと呼ぶ

hashtag: #scala_ks_main

Page 18: Reladomo in Scala #scala_ks

17

バイテンポラルデータモデルを使うと

hashtag: #scala_ks_main

Page 19: Reladomo in Scala #scala_ks

18

トランザクション時間表現

たとえシステム反映のタイミングが違ったとしても

10株 50株

10,000円 2,000円

保有株数

株価

X月Y日 S時T分 X月Y日 A時B分

hashtag: #scala_ks_main

Page 20: Reladomo in Scala #scala_ks

19

有効時間表現

ビジネス上で有効な時間を合わせることができる

10株 50株

10,000円 2,000円

保有株数

株価

資産評価額10株 x 10,000円

= 100,000円50株 x 2,000円

= 100,000円

X月Y日 0時0分

🤗

hashtag: #scala_ks_main

Page 21: Reladomo in Scala #scala_ks

20

証券会社のバックエンド機能開発の難しさ

さまざまな側面で「履歴」や「有効期間」の表現が必要になる

口座開設中の状態遷移の表現

顧客情報の変更履歴の表現

残高の履歴の表現

株式分割・併合等のコーポレート・アクションが起きた際の株価・株数変更における有効期間表現

etc.

Page 22: Reladomo in Scala #scala_ks

21

そこでReladomo

hashtag: #scala_ks_main

Page 23: Reladomo in Scala #scala_ks

22

Reladomoとは

https://github.com/goldmansachs/reladomo

ゴールドマン・サックス社が2016年9月にGitHubにOSSとして公開したJava ORMフレームワーク

Apache License 2.0

ORMフレームワークであり、オブジェクト指向の徹底

xmlからコード/DDLの自動生成

バイテンポラルデータモデルをネイティブサポート

強力に型付けられたクエリー言語(SQLは書かない)

ユニットテストのフルサポート

etc.

hashtag: #scala_ks_main

Page 24: Reladomo in Scala #scala_ks

23

Reladomoの基本

Javaにおける基本的な使用法についてはJJUGナイトセミナーのプレゼン資料をごらんください

hashtag: #scala_ks_main

Page 25: Reladomo in Scala #scala_ks

24

株式会社FOLIOにおける開発

履歴や有効時間の概念が必要となる機能要件

口座開設中の状態遷移の表現

顧客情報の変更履歴

残高の履歴の表現

株式分割・併合等のコーポレート・アクションが起きた際の株価・株数変更における有効期間表現

etc.

技術要件

Scala/Finatra/Finagleによるマイクロサービス構成

現行のquillによるデータアクセスを置き換えたい

Scalaから履歴や有効期間の自然なコード記述を実現したい

hashtag: #scala_ks_main

Page 26: Reladomo in Scala #scala_ks

25

ReladomoをScalaから自然に扱いたい

hashtag: #scala_ks_main

Page 27: Reladomo in Scala #scala_ks

26

だから

hashtag: #scala_ks_main

Page 28: Reladomo in Scala #scala_ks

27

reladomo-scalaを開発しました!

hashtag: #scala_ks_main

Page 29: Reladomo in Scala #scala_ks

28

reladomo-scalaの紹介

hashtag: #scala_ks_main

Page 30: Reladomo in Scala #scala_ks

29

reladomo-scala とは何か

以下の 2 つを提供する OSS ライブラリ

●Reladomo のコード生成に対応する sbt プラグイン

●Java コード生成

●Scala コード生成

●DDL 生成

●生成されたコードが依存する共通モジュール

●Reladomo の Scala ラッパー

●Twitter Future に対応した Scala ラッパー

hashtag: #scala_ks_main

Page 31: Reladomo in Scala #scala_ks

30

lazy val root = (project in file(“.")).settings(libraryDependencies += "com.folio-sec" %% "reladomo-scala-common" % v

).enablePlugins(ReladomoPlugin)

addSbtPlugin("com.folio-sec" % "sbt-reladomo-plugin" % v)

reladomo-scala の使い方

●Reladomo オブジェクト定義ファイル

●コンパイル時に必要

●ランタイムコンフィグファイル

●実行時、DB 接続情報とオブジェクトのキャッシュ設定

hashtag: #scala_ks_main

Page 32: Reladomo in Scala #scala_ks

31

作業フロー

●sbt compile 時

1. src/main/resources/reladomo/config 配下のXML ファイルが scan される

2. target/scala-2.12/src_managed/main に Java、Scala ソースコードを生成

3. src/main/java、src/main/scala にも編集可能なコードを生成

●XML を編集して再コンパイルして、生成されたコードを使って開発する

hashtag: #scala_ks_main

Page 33: Reladomo in Scala #scala_ks

32

Inputファイル例( Customer.xml )

<MithraObject objectType="transactional"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="reladomoobject.xsd">

<PackageName>com.folio_sec.example.domain.simpleban</PackageName><ClassName>Customer</ClassName><DefaultTable>CUSTOMER</DefaultTable>

<Attribute javaType="int" name=”customerId" columnName=”CUSTOMER_ID" primaryKey="true" primaryKeyGeneratorStrategy="Max"/>

<Attribute javaType="String" name=”name" columnName=”NAME"nullable="false" maxLength="64"/>

</MithraObject>

hashtag: #scala_ks_main

Page 34: Reladomo in Scala #scala_ks

33

ディレクトリ構成

├── build.sbt

├── project

│ └── plugins.sbt

├── src

│ ├── main

│ │ ├── java

│ │ ├── resources

│ │ │ └── reladomo

│ │ │ └── config

│ │ │ ├── Customer.xml

│ │ │ ├── CustomerAccount.xml

│ │ │ └── ReladomoClassList.xml

│ │ └── scala

│ └── test

└── target

└── scala-2.12

└── src_managed

└── main

sbtファイル

ファイル生成のためのinputファイル

hashtag: #scala_ks_main

Page 35: Reladomo in Scala #scala_ks

34

ディレクトリ構成

├── build.sbt

├── project

│ └── plugins.sbt

├── src

│ ├── main

│ │ ├── java

│ │ ├── resources

│ │ │ └── reladomo

│ │ │ └── config

│ │ │ ├── Customer.xml

│ │ │ ├── CustomerAccount.xml

│ │ │ └── ReladomoClassList.xml

│ │ └── scala

│ └── test

└── target

└── scala-2.12

└── src_managed

└── main

ソースファイルが生成される

hashtag: #scala_ks_main

Page 36: Reladomo in Scala #scala_ks

35

Reladomoの検索

hashtag: #scala_ks_main

Page 37: Reladomo in Scala #scala_ks

36

Finder API

Finderクラスを用いてOperationを生成

–型安全な検索条件のAPIを提供

SQLは一切書かない

–検索条件: Operation - Finder APIから作成

–1件検索:Finder.findOne(Operation)

–複数検索:Finder.findMany(Operation)

–Aggregationのサポート(max、sum等)

hashtag: #scala_ks_main

Page 38: Reladomo in Scala #scala_ks

37

Finder API:一件検索

Operation findTaroOp = PersonFinder.firstName().eq(“太郎");

Person taro = PersonFinder.findOne(findTaroOp);

Finder APIを用いてOperationを作成

Finder.findOne()で一件検索結果はEntityオブジェクトで取得

Page 39: Reladomo in Scala #scala_ks

38

val taro: Option[Person] = PersonFinder.findOne(findTaroOp)

val taro: Option[Person] = PersonFinder.findOneWith(_.firstName.eq(“太郎”))

Finder API:一件検索

val findTaroOp = PersonFinder.firstName.eq(“太郎")

Finder APIを用いてOperationを作成

Finder.findOne()で一件検索

結果はEntityオブジェクトで取得

Page 40: Reladomo in Scala #scala_ks

39

Finder API:複数検索

Operation findAllOp = PersonFinder.all();

PersonList people = PersonFinder.findMany(findAllOp);

Finder APIを用いてOperationを作成

Finder.findMany()で複数検索結果はListオブジェクトで取得

Page 41: Reladomo in Scala #scala_ks

40

Finder API:複数検索

val findAllOp = PersonFinder.all

val people = PersonFinder.findManyWith(_.all)

Finder APIを用いてOperationを作成

Finder.findMany()で複数検索結果はListオブジェクトで取得

Page 42: Reladomo in Scala #scala_ks

41

Finder API:Operationの例1

Operation op1 = PersonFinder.firstName().eq("大輔");// SQL: WHERE first_name = '大輔’

Operation op2 = PersonFinder.lastName().endsWith("藤");// SQL: WHERE last_name LIKE '%藤’

Operation op1OrOp2 = op1.or(op2);// SQL: WHERE (( first_name = '大輔') OR ( last_name LIKE '%藤'))

Operation op1AndOp2 = op1.and(op2);// SQL: WHERE (( first_name = '大輔') AND ( last_name LIKE '%藤'))

Page 43: Reladomo in Scala #scala_ks

42

Finder API:Operationの例1

val op1 = PersonFinder.firstName.eq("大輔")// SQL: WHERE first_name = '大輔’val op2 = PersonFinder.lastName.endsWith("藤")// SQL: WHERE last_name LIKE '%藤’val op1OrOp2 = op1 || op2// SQL: WHERE (( first_name = '大輔') OR ( last_name LIKE '%藤'))val op1AndOp2 = op1 && op2// SQL: WHERE (( first_name = '大輔') AND ( last_name LIKE '%藤'))

// Scalaでは and/or 条件が非常に柔軟に書ける!Finder.findManyWith { q => (q.firstName.eq(”XXX") || q.firstName.eq(”YYY")) && (…)) }

Page 44: Reladomo in Scala #scala_ks

43

Operation op3 =PersonFinder.age().in(IntHashSet.newSetWith(22, 24, 30));

// SQL: WHERE age in (22, 24, 30)

Operation op4 =PersonFinder.age().notIn(IntHashSet.newSetWith(22, 25));

// SQL: WHERE age in (22, 25)

Operation op5 =PersonFinder.age().greaterThan(25);

// SQL: WHERE age > 25

その他複雑なクエリを型安全に記述することが可能

Finder API:Operationの例2

Page 45: Reladomo in Scala #scala_ks

44

val op3 = PersonFinder.age.in(Set(22, 24, 30))// SQL: WHERE age in (22, 24, 30)

val op4 = PersonFinder.age.notIn(Set(22, 25))// SQL: WHERE age in (22, 25)

val op5 = PersonFinder.age.greaterThan(25)// SQL: WHERE age > 25

その他複雑なクエリを型安全に記述することが可能

Finder API:Operationの例2

Page 46: Reladomo in Scala #scala_ks

45

Reladomoのキャッシュ

hashtag: #scala_ks_main

Page 47: Reladomo in Scala #scala_ks

46

ReIadomoのキャッシュ

●Reladomoは複数のキャッシュ戦略を持つ

–Partial Cache

–Full Cache

–None●ランタイムの設定でエンティティごとにキャッシュ戦略を選択可能

●クエリーキャッシュとオブジェクトキャッシュ

●キャッシュ機構は高度に最適化されており、データベースへのアクセスを最小に保つことができる

hashtag: #scala_ks_main

Page 48: Reladomo in Scala #scala_ks

47

val taro = PersonFinder.findOne(_.firstName.eq("太郎"))

val op1 = PersonFinder.firstName.eq("大輔")val op2 = PersonFinder.lastName.endsWith("藤") val people2 = PersonFinder.findMany(op1 || op2))people2.forceResolve()

val op3 = PersonFinder.age.greaterThan(25)val people3 = PersonFinder.findMany(op3)people3.forceResolve()

3種類の条件の違うクエリーは直接DBを叩く

ReIadomoのキャッシュ

Page 49: Reladomo in Scala #scala_ks

48

2017-07-23 18:02:06:881 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:2005169944 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 where t0.first_name = '太郎’2017-07-23 18:02:06:891 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 1 objects, 83.0 ms per2017-07-23 18:02:06:992 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:2005169944 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 where (( t0.first_name = '大輔') or ( t0.last_name like '%藤'))2017-07-23 18:02:06:994 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 3 objects, 1.3333333333333333 ms per2017-07-23 18:02:06:997 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:2005169944 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 where t0.age > 252017-07-23 18:02:07:005 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 1 objects, 8.0 ms per

ReIadomoのキャッシュ

3回のDBアクセス

hashtag: #scala_ks_main

Page 50: Reladomo in Scala #scala_ks

49

val people = PersonFinder.findManyWith(_.all)

val findTaroOp = PersonFinder.firstName.eq(“太郎")… 前ページと同じ3つのクエリー

前ページと同じ3種類のクエリーの直前に全選択のクエリーを走らせると…

ReIadomoのキャッシュ

Page 51: Reladomo in Scala #scala_ks

50

2017-07-23 19:34:04:415 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:112049309 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t02017-07-23 19:34:04:458 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 4 objects, 25.5 ms per

ReIadomoのキャッシュ

1回のDBアクセスで全選択

●DBへのIOは全選択クエリーの1回のみ

●その後のクエリーはキャッシュから取得

hashtag: #scala_ks_main

Page 52: Reladomo in Scala #scala_ks

51

Reladomoのトランザクション処理

hashtag: #scala_ks_main

Page 53: Reladomo in Scala #scala_ks

52

Reladomoのトランザクション処理

トランザクション内で行う必要のある処理に関してはラムダ式内に記述しexecuteTransactionalCommand()にわたす。以下、例によってはこのトランザクションのコードを省略している。

MithraManagerProvider.getMithraManager().executeTransactionalCommand(tx -> {

//検索・挿入・更新・削除処理の記述

return someObject;});

Page 54: Reladomo in Scala #scala_ks

53

Reladomoのトランザクション処理

Reladomo は ThreadLocal に MithraTransaction がいないと実行時例外になる場合がある(ID 自動採番など)reladomo-scala は更新系処理は全て implicit parameter を要求するようにしてコンパイル時に気づけるようにした。

TranscationProvider.withTransaction { implicit tx =>//検索・挿入・更新・削除処理の記述// reladomo-scala では return null; する必要はない

});

Page 55: Reladomo in Scala #scala_ks

54

Reladomoの挿入処理

hashtag: #scala_ks_main

Page 56: Reladomo in Scala #scala_ks

55

Reladomoの挿入処理:単一挿入(Scala)

val jiro = NewPerson("二郎", "山田", 45)

jiro.insert()

Entityインスタンスを作成

Insert()メソッドで挿入

Page 57: Reladomo in Scala #scala_ks

56

NewPersonList(Seq(NewPerson(“二郎”, "山田", 45),NewPerson("さくら", "鈴木", 28)

)).insertAll()

Reladomoの挿入処理:バッチ挿入

Listインスタンスを作成

Page 58: Reladomo in Scala #scala_ks

57

Reladomoの更新処理

hashtag: #scala_ks_main

Page 59: Reladomo in Scala #scala_ks

58

val tanaka = PersonFinder.findOneWith(_.lastName.eq("田中"))

tanaka.copy(age = 25).update()

Reladomoの更新処理:単一更新

Entityインスタンスを検索

setter で更新しない update/delete 呼ぶまでは DB に反映はしない

バイテンポラルの場合も有効時間の指定以外はほぼ同じコードで表現できる(後述)

Page 60: Reladomo in Scala #scala_ks

59

Reladomoの更新処理:複数更新

複数検索でListオブジェクトを取得

val people = PersonFinder.findManyWith(_.all)people.withAge(20).updateAll()

2017-07-23 21:45:08:489 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - multi update of 6 objects with: update person set age = ? where person_id in (?...)2017-07-23 21:45:08:489 [main] DEBUG com.gs.fw.common.mithra.batch.sqllogs.Person - multi updating with: update person set age = 20 where person_id in (0,1,2,3,4,5)

Page 61: Reladomo in Scala #scala_ks

60

Reladomoの更新処理:バッチ更新

Entityインスタンスを検索

トランザクション内での複数更新はバッチ更新される

val tanaka = PersonFinder.findOneWith(_.lastName.eq(“田中”))val sato = PersonFinder.findOneWith(_.lastName.eq(“佐藤”))

TransactionProvider.withTransaction { implicit tx =>tanaka.copy(age = 25).update()sato.copy(age = 23).update()

}

2017-07-23 21:38:39:403 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - batch update of 2 objects with: update person set age = ? where person_id = ?2017-07-23 21:38:39:404 [main] DEBUG com.gs.fw.common.mithra.batch.sqllogs.Person - batch updating with: update person set age = 25 where person_id = 02017-07-23 21:38:39:405 [main] DEBUG com.gs.fw.common.mithra.batch.sqllogs.Person - batch updating with: update person set age = 23 where person_id = 1

Page 62: Reladomo in Scala #scala_ks

61

Reladomoの削除処理

hashtag: #scala_ks_main

Page 63: Reladomo in Scala #scala_ks

62

Reladomoの削除処理:単一削除

Entityインスタンスを検索

delete()メソッドで削除

val tanaka = PersonFinder.findOneWith(_.lastName.eq(“田中”))

tanaka.delete()

Page 64: Reladomo in Scala #scala_ks

63

Reladomoの削除処理:複数削除

複数検索でListオブジェクトを取得

Listに対してdeleteAll()メソッドで複数削除

val people = PersonFinder.findManyWith(_.lastName.in(Set(“田中”, “佐藤"))

people.deleteAll()

Page 65: Reladomo in Scala #scala_ks

64

Reladomoの関連

hashtag: #scala_ks_main

Page 66: Reladomo in Scala #scala_ks

65

Reladomoの関連:サンプルモデル hashtag: #scala_ks_main

Page 67: Reladomo in Scala #scala_ks

66

Reladomoの関連:Person.xml

<Relationship name="pets"relatedObject="Pet"cardinality="one-to-many"relatedIsDependent="true"reverseRelationshipName="owner">

this.personId = Pet.ownerId</Relationship>

Person PetPetPetpets

owner

hashtag: #scala_ks_main

Page 68: Reladomo in Scala #scala_ks

67

Reladomoの関連:Pet.xml

<Relationship name="petType"relatedObject="PetType"cardinality="many-to-one">

this.petTypeId = PetType.petTypeId</Relationship>

Pet PetTypepetType

hashtag: #scala_ks_main

Page 69: Reladomo in Scala #scala_ks

68

Reladomoの関連:Finderによる柔軟な検索1

//犬を飼っている飼い主を取得val op =

PersonFinder.pets.petTypeId.eq(PetType.DOG)

val dogOwner = PersonFinder.findOneWith(_.pets.petTypeId.eq(PetType.DOG))

テーブル間のjoinを柔軟に記述

通常の一件検索と同様

Page 70: Reladomo in Scala #scala_ks

69

Reladomoの関連:Finderによる柔軟な検索1

2017-07-24 21:51:55:924 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:1954406292 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 inner join (select distinct t1.owner_id c0 from pet t1 where t1.pet_type_id = 0) as d1 on t0.person_id = d1.c02017-07-24 21:51:55:977 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 1 objects, 347.0 ms per

生成されるSQL

Page 71: Reladomo in Scala #scala_ks

70

//佐藤さんが飼っているペットを取得val op =

PetFinder.owner.lastName.eq("佐藤")

val satoPets = PetFinder.findManyWith(_.lastName.eq(“佐藤"))

通常の複数検索と同様

Reladomoの関連:Finderによる柔軟な検索2

Page 72: Reladomo in Scala #scala_ks

71

Reladomoの関連:Finderによる柔軟な検索2

2017-07-25 07:09:31:305 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - connection:708533063 find with: select t0.pet_id,t0.name,t0.owner_id,t0.age,t0.pet_type_id from pet t0 inner join person t1 on t0.owner_id = t1.person_id where t1.last_name = '佐藤’2017-07-25 07:09:31:316 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - retrieved 2 objects, 49.5 ms per

生成されるSQL

Page 73: Reladomo in Scala #scala_ks

72

Reladomoの関連:deepFetch

Relationshipを解決する際のN+1問題を避けるための機構

//ペット飼っている人を取得val op = PersonFinder.pets.exists()val petOwners = PersonFinder.findMany(op)

petOwners.deepFetch(PersonFinder.pets)petOwners.deepFetch(PersonFinder.pets.petType)

petOwners.foreach { petOwner =>petOwner.getPets.forEach { pet =>

println(petOwner.lastName + "さんは" +pet.name + "という名の" +pet.petType.petType + "を飼っています")

}});

取得したい関連をdeepFetch指定

Page 74: Reladomo in Scala #scala_ks

73

Reladomoの関連:deepFetch

deepFetchを使わない場合に発行されるSQL

2017-07-25 07:23:05:473 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - connection:576020159 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 inner join (select distinct t1.owner_id c0 from pet t1) as d1 on t0.person_id = d1.c02017-07-25 07:23:05:493 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 2 objects, 51.0 ms per2017-07-25 07:23:05:604 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - connection:576020159 find with: select t0.pet_id,t0.name,t0.owner_id,t0.age,t0.pet_type_id from pet t0 where t0.owner_id = 12017-07-25 07:23:05:608 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - retrieved 2 objects, 6.0 ms per2017-07-25 07:23:05:645 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:576020159 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id = 02017-07-25 07:23:05:647 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 1 objects, 3.0 ms per2017-07-25 07:23:05:658 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:576020159 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id = 32017-07-25 07:23:05:658 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 1 objects, 2.0 ms per2017-07-25 07:23:05:659 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - connection:576020159 find with: select t0.pet_id,t0.name,t0.owner_id,t0.age,t0.pet_type_id from pet t0 where t0.owner_id = 22017-07-25 07:23:05:660 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - retrieved 2 objects, 0.5 ms per2017-07-25 07:23:05:661 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:576020159 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id = 12017-07-25 07:23:05:662 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 1 objects, 2.0 ms per2017-07-25 07:23:05:663 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:576020159 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id = 22017-07-25 07:23:05:664 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 1 objects, 1.0 ms per

hashtag: #scala_ks_main

Page 75: Reladomo in Scala #scala_ks

74

Reladomoの関連:deepFetch

deepFetchを使った場合に発行されるSQL

2017-07-25 07:27:16:540 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - connection:587153993 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 inner join (select distinct t1.owner_id c0 from pet t1) as d1 on t0.person_id = d1.c02017-07-25 07:27:16:564 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 2 objects, 58.0 ms per2017-07-25 07:27:16:650 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - connection:587153993 find with: select t0.pet_id,t0.name,t0.owner_id,t0.age,t0.pet_type_id from pet t0 where t0.owner_id in ( 1,2)2017-07-25 07:27:16:652 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - retrieved 4 objects, 1.75 ms per2017-07-25 07:27:16:661 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:587153993 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id in ( 0,1,2,3)2017-07-25 07:27:16:666 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 4 objects, 1.25 ms per

hashtag: #scala_ks_main

Page 76: Reladomo in Scala #scala_ks

75

reladomo-scalaのリリースポリシー

https://groups.google.com/forum/#!topic/finaglers/AaQsOXYm664

互換性のあるバージョンごとにビルド

–Reladomo version: 16.5.X

–twitter-util-core: 7.1.X

hashtag: #scala_ks_main

Page 77: Reladomo in Scala #scala_ks

76

reladomo-scala ToDo

reverseRelationshipNameのサポート

Unit test サポート

Multi-Threaded matcher loaderのサポート

生成されたドメインクラスの拡張サポート

Business date指定もれのコンパイル時検知

hashtag: #scala_ks_main

Page 78: Reladomo in Scala #scala_ks

77

バイテンポラルデータモデル

hashtag: #scala_ks_main

Page 79: Reladomo in Scala #scala_ks

78

RDBMSで履歴データを扱う

単純なシナリオを考えてみましょう

シンプルな人事システム。扱う情報は姓名のみ。

4月1日に「鈴木花子」さん入社。3月1日にシステムに登録

6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録

6月8日にシステム上で修正(斉藤=>斎藤)。事実としては6月6日づけで直したい

12月1日、退職にともない同日にシステム上で情報を無効化

hashtag: #scala_ks_main

Page 80: Reladomo in Scala #scala_ks

79

RDBMSで履歴データを扱う

シナリオをよく観察すると、2種類の履歴が存在

2種類の時間表現なのでbi-temporal:バイテンポラル

3/1 4/1 6/6 6/8 12/1

鈴木花子

事実情報の履歴

システムに反映された

履歴

入社🎉

「鈴木花子」をシステムに登録

結婚して斎藤になる👰

名字を「斉藤」に変更

名字を「斎藤」に変更

退職†

データを無効化

hashtag: #scala_ks_main

Page 81: Reladomo in Scala #scala_ks

80

バイテンポラルデータモデル

姓 名 FROM THRU IN OUT

鈴木 花子 2017/4/1 9999/12/1 2017/3/1 9999/12/1

4月1日に「鈴木花子」さん入社。3月1日にシステムに登録

鈴木さん入社!

4月1日に入社したこともわかるし、3月1日にシステムに反映されたこともわかる!

hashtag: #scala_ks_main

Page 82: Reladomo in Scala #scala_ks

81

バイテンポラルデータモデル

姓 名 FROM THRU IN OUT

鈴木 花子 2017/4/1 9999/12/1 2017/3/1 2017/6/6

鈴木 花子 2017/4/1 2017/6/6 2017/6/6 9999/12/1

斉藤 花子 2017/6/6 9999/12/1 2017/6/6 9999/12/1

6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録

最初の行が「システム上」無効になり、新しい「事実」が2つの期間にわたって挿入されているね。

hashtag: #scala_ks_main

Page 83: Reladomo in Scala #scala_ks

82

バイテンポラルデータモデル

姓 名 FROM THRU IN OUT

鈴木 花子 2017/4/1 9999/12/1 2017/3/1 2017/6/6

鈴木 花子 2017/4/1 2017/6/6 2017/6/6 9999/12/1

斉藤 花子 2017/6/6 9999/12/1 2017/6/6 2017/6/8

斎藤 花子 2017/6/6 9999/12/1 2017/6/8 9999/12/1

名字の漢字が間違っており、6月8日にシステム上で修正(斉藤=>斎藤)

間違えた行が「システム上」無効になり、正しい「事実」が挿入されているね。

3つ目の行は「斉藤」という間違った事実が「システム上有効だった」期間が6/6から6/8の間存在するという情報を表しているよ。

hashtag: #scala_ks_main

Page 84: Reladomo in Scala #scala_ks

83

バイテンポラルデータモデル

姓 名 FROM THRU IN OUT

鈴木 花子 2017/4/1 9999/12/1 2017/3/1 2017/6/6

鈴木 花子 2017/4/1 2017/6/6 2017/6/6 9999/12/1

斉藤 花子 2017/6/6 9999/12/1 2017/6/6 2017/6/8

斎藤 花子 2017/6/6 9999/12/1 2017/6/8 2017/12/1

斎藤 花子 2017/6/6 2017/12/1 2017/12/1 9999/12/1

12月1日、退職にともない同日に人事情報をシステム上で無効化

無事、事実情報(入社、姓変更、退社)と変更履歴がすべて記録されました!

hashtag: #scala_ks_main

Page 85: Reladomo in Scala #scala_ks

84

<MithraObject objectType=“transactional”xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”xsi:noNamespaceSchemaLocation=“reladomoobject.xsd”>

<PackageName>sample.domain</PackageName><ClassName>Employee</ClassName><DefaultTable>EMPLOYEE</DefaultTable>

<AsOfAttribute name=“processingDate” fromColumnName=“IN_Z” toColumnName=“OUT_Z”toIsInclusive=“false”isProcessingDate=“true”timezoneConversion=“none”infinityDate=“[com.gs.fw.common.mithra.util.DefaultInfinityTimestamp.getDefaultInfinity()]”defaultIfNotSpecified=“[com.gs.fw.common.mithra.util.DefaultInfinityTimestamp.getDefaultInfinity()]”

/>

<AsOfAttribute name=“businessDate” fromColumnName=“FROM_Z” toColumnName=“THRU_Z”toIsInclusive=“false”isProcessingDate=“false”timezoneConversion=“none”infinityDate=“[com.gs.fw.common.mithra.util.DefaultInfinityTimestamp.getDefaultInfinity()]”futureExpiringRowsExist=“true”

/>

・・・省略・・・</MithraObject>

バイテンポラルを使用する際の設定例 hashtag: #scala_ks_main

トランザクション時間

有効時間

Page 86: Reladomo in Scala #scala_ks

85

val saito = EmployeeFinder.findOneWith { q => q.lastName.eq("斉藤") && q.businessDate.eq(now())}

saito.copy(lastName ="斎藤”).update()

バイテンポラルのケースでの単一更新例

有効時間の指定

通常の場合と同様にupdateを呼ぶだけで、データモデル上のIN/OUT/THRU/FROMを自動で更新・挿入

Page 88: Reladomo in Scala #scala_ks

87

reladomo-scalaのOSS公開!

hashtag: #scala_ks_main

Page 89: Reladomo in Scala #scala_ks

88

https://github.com/folio-sec/reladomo-scala/

リアルタイムに公開します🤗

reladomo-scalaのOSS公開! hashtag: #scala_ks_main

Page 90: Reladomo in Scala #scala_ks

89

株式会社FOLIOでの導入事例

Dealing Engineのポジション履歴サービスのデータアクセスのreladomo-scala導入

今後の株式会社FOLIOでの導入予定

口座開設中の状態遷移の表現

顧客情報の変更履歴

残高の履歴の表現

株式分割・併合等のコーポレート・アクションが起きた際の株価・株数変更における有効期間表現

etc.

reladomo-scala の導入事例 hashtag: #scala_ks_main

Page 91: Reladomo in Scala #scala_ks

90

Reladomoを学ぶには

つづきはReladomo Kata / Reladomo Tourで

Reladomo Kata GitHub (Reladomo チュートリアル)

Guided Tour of Reladomo

Reladomo Kataは、JUnit上でテストをパスしながら学べるトレーニング教材

Javaで試してみたい方はKata内のmini-kataで手を動かして試してみることをおススメします

Page 92: Reladomo in Scala #scala_ks

91

さらに reladomo-scala を学ぶには

giter8 templateで遊んで見てください!

(こちらもセッション中に公開されます)

https://github.com/folio-sec/reladomo-first-example.g8/

# sbt new folio-sec/reladomo-first-example.g8

Page 93: Reladomo in Scala #scala_ks

92

株式会社FOLIO

https://www.wantedly.com/projects/151883

Scala エンジニア募集中!

hashtag: #scala_ks_main

Page 94: Reladomo in Scala #scala_ks

93

APPENDIX

Page 95: Reladomo in Scala #scala_ks

94

リンク集

reladomo-scala

reladomo-scala giter8 template

データ履歴管理のためのテンポラルデータモデルとReladomoの紹介

Reladomo入門

Reladomo GitHub

Reladomo Kata GitHub (Reladomo チュートリアル)

Guided Tour of Reladomo

Reladomo Documentations