Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Reladomo in Scala #scala_ks

7,374 views

Published on

Reladomo in Scala
FOLIO 伊藤博志 & Good Flow Technologies 瀬良和弘

Published in: Engineering
  • Be the first to comment

Reladomo in Scala #scala_ks

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

×