reladomo in scala #scala_ks
TRANSCRIPT
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
1
Agenda
1. 自己紹介
2. 株式会社FOLIOでの開発とReladomo
3. scala-reladomoの紹介
4. バイテンポラルモデル
5. scala-reladomoの… OSS公開!!!!
hashtag: #scala_ks_main
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
3
Reladomoとはなにか
hashtag: #scala_ks_main
4
を説明する前に
hashtag: #scala_ks_main
5
Reladomoに解決してほしい問題
の例を挙げてみます
hashtag: #scala_ks_main
7
証券システム開発の難しさ
hashtag: #scala_ks_main
8
いきなり難しい例を出します
hashtag: #scala_ks_main
9
たとえば株式分割等のコーポレートアクションの表現
1株A社1株10,000円
1株1株1株A社 1株2,000円
1株 5株
X月Y日コーポレートアクション
1:5の株式分割X月Y日を境に起きる
10
たとえば株式分割等のコーポレートアクションの表現
1:5の株式分割システム上のお客様の保有株数の変化ある特定の日時X月Y日 S時T分に反映される
A社 10株 A社 50株
X月Y日 S時T分バッチジョブによる株数変更
11
たとえば株式分割等のコーポレートアクションの表現
A社株10,000円
A社株2,000円
X月Y日 A時B分DBに反映
1:5の株式分割データソースベンダーから受け取る株価の変化ある特定の日時X月Y日 A時B分に反映される
コーポレートアクション
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分
13
RDBMSで履歴・有効期間データを扱う
hashtag: #scala_ks_main
14
RDBMSで履歴データを扱う
単純なシナリオを考えてみましょう
シンプルな人事システム。扱う情報は姓名のみ。
4月1日に「鈴木花子」さん入社。3月1日にシステムに登録
6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録
6月8日にシステム上で修正(斉藤=>斎藤)。事実としては6月6日づけで直したい
12月1日、退職にともない同日にシステム上で情報を無効化
hashtag: #scala_ks_main
15
RDBMSで履歴データを扱う
シナリオをよく観察すると、2種類の履歴が存在
2種類の時間表現なのでbi-temporal:バイテンポラル
3/1 4/1 6/6 6/8 12/1
鈴木花子
事実情報の履歴
システムに反映された
履歴
入社🎉
「鈴木花子」をシステムに登録
結婚して斎藤になる👰
名字を「斉藤」に変更
名字を「斎藤」に変更
退職†
データを無効化
このような、履歴や有効期間情報を扱えるデータモデルを
テンポラルデータモデル、上記のような2種類の時間表現が可能なモデルをバイテンポラルデータモデルと呼ぶ
hashtag: #scala_ks_main
16
バイテンポラルモデル
詳しくはJJUG CCCのプレゼン資料をごらんください
hashtag: #scala_ks_main
17
バイテンポラルデータモデルを使うと
hashtag: #scala_ks_main
18
トランザクション時間表現
たとえシステム反映のタイミングが違ったとしても
10株 50株
10,000円 2,000円
保有株数
株価
X月Y日 S時T分 X月Y日 A時B分
hashtag: #scala_ks_main
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
20
証券会社のバックエンド機能開発の難しさ
さまざまな側面で「履歴」や「有効期間」の表現が必要になる
口座開設中の状態遷移の表現
顧客情報の変更履歴の表現
残高の履歴の表現
株式分割・併合等のコーポレート・アクションが起きた際の株価・株数変更における有効期間表現
etc.
21
そこでReladomo
hashtag: #scala_ks_main
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
23
Reladomoの基本
Javaにおける基本的な使用法についてはJJUGナイトセミナーのプレゼン資料をごらんください
hashtag: #scala_ks_main
24
株式会社FOLIOにおける開発
履歴や有効時間の概念が必要となる機能要件
口座開設中の状態遷移の表現
顧客情報の変更履歴
残高の履歴の表現
株式分割・併合等のコーポレート・アクションが起きた際の株価・株数変更における有効期間表現
etc.
技術要件
Scala/Finatra/Finagleによるマイクロサービス構成
現行のquillによるデータアクセスを置き換えたい
Scalaから履歴や有効期間の自然なコード記述を実現したい
hashtag: #scala_ks_main
25
ReladomoをScalaから自然に扱いたい
hashtag: #scala_ks_main
26
だから
hashtag: #scala_ks_main
27
reladomo-scalaを開発しました!
hashtag: #scala_ks_main
28
reladomo-scalaの紹介
hashtag: #scala_ks_main
29
reladomo-scala とは何か
以下の 2 つを提供する OSS ライブラリ
●Reladomo のコード生成に対応する sbt プラグイン
●Java コード生成
●Scala コード生成
●DDL 生成
●生成されたコードが依存する共通モジュール
●Reladomo の Scala ラッパー
●Twitter Future に対応した Scala ラッパー
hashtag: #scala_ks_main
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
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
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
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
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
35
Reladomoの検索
hashtag: #scala_ks_main
36
Finder API
Finderクラスを用いてOperationを生成
–型安全な検索条件のAPIを提供
SQLは一切書かない
–検索条件: Operation - Finder APIから作成
–1件検索:Finder.findOne(Operation)
–複数検索:Finder.findMany(Operation)
–Aggregationのサポート(max、sum等)
hashtag: #scala_ks_main
37
Finder API:一件検索
Operation findTaroOp = PersonFinder.firstName().eq(“太郎");
Person taro = PersonFinder.findOne(findTaroOp);
Finder APIを用いてOperationを作成
Finder.findOne()で一件検索結果はEntityオブジェクトで取得
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オブジェクトで取得
39
Finder API:複数検索
Operation findAllOp = PersonFinder.all();
PersonList people = PersonFinder.findMany(findAllOp);
Finder APIを用いてOperationを作成
Finder.findMany()で複数検索結果はListオブジェクトで取得
40
Finder API:複数検索
val findAllOp = PersonFinder.all
val people = PersonFinder.findManyWith(_.all)
Finder APIを用いてOperationを作成
Finder.findMany()で複数検索結果はListオブジェクトで取得
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 '%藤'))
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")) && (…)) }
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
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
45
Reladomoのキャッシュ
hashtag: #scala_ks_main
46
ReIadomoのキャッシュ
●Reladomoは複数のキャッシュ戦略を持つ
–Partial Cache
–Full Cache
–None●ランタイムの設定でエンティティごとにキャッシュ戦略を選択可能
●クエリーキャッシュとオブジェクトキャッシュ
●キャッシュ機構は高度に最適化されており、データベースへのアクセスを最小に保つことができる
hashtag: #scala_ks_main
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のキャッシュ
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
49
val people = PersonFinder.findManyWith(_.all)
val findTaroOp = PersonFinder.firstName.eq(“太郎")… 前ページと同じ3つのクエリー
前ページと同じ3種類のクエリーの直前に全選択のクエリーを走らせると…
ReIadomoのキャッシュ
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
51
Reladomoのトランザクション処理
hashtag: #scala_ks_main
52
Reladomoのトランザクション処理
トランザクション内で行う必要のある処理に関してはラムダ式内に記述しexecuteTransactionalCommand()にわたす。以下、例によってはこのトランザクションのコードを省略している。
MithraManagerProvider.getMithraManager().executeTransactionalCommand(tx -> {
//検索・挿入・更新・削除処理の記述
return someObject;});
53
Reladomoのトランザクション処理
Reladomo は ThreadLocal に MithraTransaction がいないと実行時例外になる場合がある(ID 自動採番など)reladomo-scala は更新系処理は全て implicit parameter を要求するようにしてコンパイル時に気づけるようにした。
TranscationProvider.withTransaction { implicit tx =>//検索・挿入・更新・削除処理の記述// reladomo-scala では return null; する必要はない
});
54
Reladomoの挿入処理
hashtag: #scala_ks_main
55
Reladomoの挿入処理:単一挿入(Scala)
val jiro = NewPerson("二郎", "山田", 45)
jiro.insert()
Entityインスタンスを作成
Insert()メソッドで挿入
56
NewPersonList(Seq(NewPerson(“二郎”, "山田", 45),NewPerson("さくら", "鈴木", 28)
)).insertAll()
Reladomoの挿入処理:バッチ挿入
Listインスタンスを作成
57
Reladomoの更新処理
hashtag: #scala_ks_main
58
val tanaka = PersonFinder.findOneWith(_.lastName.eq("田中"))
tanaka.copy(age = 25).update()
Reladomoの更新処理:単一更新
Entityインスタンスを検索
setter で更新しない update/delete 呼ぶまでは DB に反映はしない
バイテンポラルの場合も有効時間の指定以外はほぼ同じコードで表現できる(後述)
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)
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
61
Reladomoの削除処理
hashtag: #scala_ks_main
62
Reladomoの削除処理:単一削除
Entityインスタンスを検索
delete()メソッドで削除
val tanaka = PersonFinder.findOneWith(_.lastName.eq(“田中”))
tanaka.delete()
63
Reladomoの削除処理:複数削除
複数検索でListオブジェクトを取得
Listに対してdeleteAll()メソッドで複数削除
val people = PersonFinder.findManyWith(_.lastName.in(Set(“田中”, “佐藤"))
people.deleteAll()
64
Reladomoの関連
hashtag: #scala_ks_main
65
Reladomoの関連:サンプルモデル hashtag: #scala_ks_main
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
67
Reladomoの関連:Pet.xml
<Relationship name="petType"relatedObject="PetType"cardinality="many-to-one">
this.petTypeId = PetType.petTypeId</Relationship>
Pet PetTypepetType
hashtag: #scala_ks_main
68
Reladomoの関連:Finderによる柔軟な検索1
//犬を飼っている飼い主を取得val op =
PersonFinder.pets.petTypeId.eq(PetType.DOG)
val dogOwner = PersonFinder.findOneWith(_.pets.petTypeId.eq(PetType.DOG))
テーブル間のjoinを柔軟に記述
通常の一件検索と同様
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
70
//佐藤さんが飼っているペットを取得val op =
PetFinder.owner.lastName.eq("佐藤")
val satoPets = PetFinder.findManyWith(_.lastName.eq(“佐藤"))
通常の複数検索と同様
Reladomoの関連:Finderによる柔軟な検索2
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
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指定
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
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
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
76
reladomo-scala ToDo
reverseRelationshipNameのサポート
Unit test サポート
Multi-Threaded matcher loaderのサポート
生成されたドメインクラスの拡張サポート
Business date指定もれのコンパイル時検知
hashtag: #scala_ks_main
77
バイテンポラルデータモデル
hashtag: #scala_ks_main
78
RDBMSで履歴データを扱う
単純なシナリオを考えてみましょう
シンプルな人事システム。扱う情報は姓名のみ。
4月1日に「鈴木花子」さん入社。3月1日にシステムに登録
6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録
6月8日にシステム上で修正(斉藤=>斎藤)。事実としては6月6日づけで直したい
12月1日、退職にともない同日にシステム上で情報を無効化
hashtag: #scala_ks_main
79
RDBMSで履歴データを扱う
シナリオをよく観察すると、2種類の履歴が存在
2種類の時間表現なのでbi-temporal:バイテンポラル
3/1 4/1 6/6 6/8 12/1
鈴木花子
事実情報の履歴
システムに反映された
履歴
入社🎉
「鈴木花子」をシステムに登録
結婚して斎藤になる👰
名字を「斉藤」に変更
名字を「斎藤」に変更
退職†
データを無効化
hashtag: #scala_ks_main
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
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
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
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
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
トランザクション時間
有効時間
85
val saito = EmployeeFinder.findOneWith { q => q.lastName.eq("斉藤") && q.businessDate.eq(now())}
saito.copy(lastName ="斎藤”).update()
バイテンポラルのケースでの単一更新例
有効時間の指定
通常の場合と同様にupdateを呼ぶだけで、データモデル上のIN/OUT/THRU/FROMを自動で更新・挿入
86
バイテンポラルモデル(再掲)
詳しくはJJUG CCCのプレゼン資料をごらんください
hashtag: #scala_ks_main
87
reladomo-scalaのOSS公開!
hashtag: #scala_ks_main
88
https://github.com/folio-sec/reladomo-scala/
リアルタイムに公開します🤗
reladomo-scalaのOSS公開! hashtag: #scala_ks_main
89
株式会社FOLIOでの導入事例
Dealing Engineのポジション履歴サービスのデータアクセスのreladomo-scala導入
今後の株式会社FOLIOでの導入予定
口座開設中の状態遷移の表現
顧客情報の変更履歴
残高の履歴の表現
株式分割・併合等のコーポレート・アクションが起きた際の株価・株数変更における有効期間表現
etc.
reladomo-scala の導入事例 hashtag: #scala_ks_main
90
Reladomoを学ぶには
つづきはReladomo Kata / Reladomo Tourで
Reladomo Kata GitHub (Reladomo チュートリアル)
Guided Tour of Reladomo
Reladomo Kataは、JUnit上でテストをパスしながら学べるトレーニング教材
Javaで試してみたい方はKata内のmini-kataで手を動かして試してみることをおススメします
91
さらに reladomo-scala を学ぶには
giter8 templateで遊んで見てください!
(こちらもセッション中に公開されます)
https://github.com/folio-sec/reladomo-first-example.g8/
# sbt new folio-sec/reladomo-first-example.g8
92
株式会社FOLIO
https://www.wantedly.com/projects/151883
Scala エンジニア募集中!
hashtag: #scala_ks_main
93
APPENDIX
94
リンク集
reladomo-scala
reladomo-scala giter8 template
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介
Reladomo入門
Reladomo GitHub
Reladomo Kata GitHub (Reladomo チュートリアル)
Guided Tour of Reladomo
Reladomo Documentations