DDD × CQRS 更新系と参照系で異なるORMを併用して上手くいった話

1,019 views

Published on

新規プロダクトのアーキテクチャにてDDDを中心として設計を行った際、参照系/更新系でORMを分けた結果、非常にメンテナンス性が高いものができました。

Published in: Technology
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,019
On SlideShare
0
From Embeds
0
Number of Embeds
116
Actions
Shares
0
Downloads
2
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

DDD × CQRS 更新系と参照系で異なるORMを併用して上手くいった話

  1. 1. DDD x CQRS - 更新系と参照系で異なるORMを併用して上手くいった話 2017/09/19 株式会社ビズリーチ 松岡 幸一郎
  2. 2. 発表者紹介 ● 松岡 幸一郎
  3. 3. 主にサーバーサイド
  4. 4. フロントは最近入門
  5. 5. プロジェクト経歴 銀行システム4年 若手向け転職サイト 2年 社内システム半年
  6. 6. 主な技術 Excel Java
  7. 7. Excelはもういい
  8. 8. 主な技術トピック Excel Java
  9. 9. 開発楽しい! 主な技術トピック Excel Java
  10. 10. しかし待ち受ける技術的負債との戦い
  11. 11. 主な技術トピック Excel Java SAStruts jQuery Java
  12. 12. 万全の対策
  13. 13. 主な技術トピック Excel Java SAStruts jQuery Spring Boot DDD Vue.js Java
  14. 14. DDD x CQRS 更新系と参照系で 異なるORMを併用して 上手くいった話 テーマ
  15. 15. CQRS DDD ORM に興味がある方 導入検討している方 想定対象者
  16. 16. アーキテクチャ系の話あるある
  17. 17. 抽象的な話がひたすら続いてよくわからない。。
  18. 18. ● 今日のゴール:このアーキテクチャの説明をすること アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure Business Logic Data User Interface 一般的な3層アーキテクチャ DDD, CQRSを組み込んだアーキテクチャ
  19. 19. ● 今日のゴール:このアーキテクチャの説明をすること アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <<iterface>> Repository Entity Repository Impl <<iterface>> Query Serivce DTO Query Serivce Impl Hibernate jOOQ
  20. 20. アジェンダ ● CQRSがなぜ必要か ○ CQRSとは何か ○ なぜ必要か ● CQRSのCommandとQueryでそれぞれどのORMを使うか ○ ORMのパターン ○ 今回の選定基準 ● CQRSをDDDにどう組み込むか ○ DDDとは何か ○ どのようなレイヤー構成になるか ○ CQRSをどう組み込むか ○ コードサンプル ● 所感・まとめ
  21. 21. アジェンダ ● CQRSがなぜ必要か ○ CQRSとは何か ○ なぜ必要か ● CQRSのCommandとQueryでそれぞれどのORMを使うか ○ ORMのパターン ○ 今回の選定基準 ● CQRSをDDDにどう組み込むか ○ DDDとは何か ○ どのようなレイヤー構成になるか ○ CQRSをどう組み込むか ○ コードサンプル ● 所感・まとめ
  22. 22. CQRSとは? ● Command and Query Responsibility Segregation (コマンド・クエリ責務分離) ● 2010年 Greg Young氏が考案したパターン ○ クエリ: 結果のみを返し、状態の変更は行わない ○ コマンド: 状態の変更のみを行い、結果は返さない ○ 「 これは、質問をすることで回答を変化させてはならないということだ。」 ● ネット上の文献は、英語で探した方がまとまった記事があるのでオススメ ○ https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs
  23. 23. ● 従来のシステムでは、コマンド(データの書き込み)とクエリ(データの読み込み) の両方が、単一のデータストア内の同じモデルを使用して実行される ● scaffoldのような自動生成の仕組みで作成したモデルが使用されることも CQRS概要
  24. 24. CQRSが必要な背景 ● 書き込み・読み込みで要件が大きく異なる ● 書き込み、読み込みで適した表現は必ずしも一致しない ● 同時にセキュリティ制御をしようとすると複雑に ● パフォーマンス要件は異なることが多い 書き込み 読み込み データの整合性維持 データの検索と抽出の効率化 アトミックな更新・トランザクション 導出値(合計など)の計算 バージョン管理 複数のビューの提供 書き込み権限の管理 行レベル、カラムレベルの権限管理 全リクエストの内占める割合は小さい 全リクエストの内占める割合は大きい 参考:http://postd.cc/using-cqrs-with-event-sourcing/
  25. 25. CQRS - 単一物理データストアモデル ● 書き込みと読み込みのIFを別物として用意する ● 書き込み・読み込みでモデルを分離することも可能(ただし必須ではない) ● 参照モデルは、SQL viewの作成、任意のクエリ発行などによる Write IF Read IF
  26. 26. CQRS - 複数物理データストアモデル ● 書き込みと読み込みのデータストアを物理的に分離する ● 参照ストアはread-onlyのレプリカや、全く別の機構を選択することも可能 (参照系はelastic searchにするなど) ● 参照/更新のストア分離により、それぞれの負荷に合わせたスケーリングが可能 ● セキュリティ制御も個別に制御がしやすくなる
  27. 27. CQRS - イベントソーシング ● さらに進めるとイベントソーシングの話が出てきますが、 今回は割愛。 ● 参考 https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing
  28. 28. 再掲:書き込み・読み込みの要件の違い 書き込み 読み込み データの整合性維持 データの検索と抽出の効率化 アトミックな更新・トランザクション 導出値(合計など)の計算 バージョン管理 複数のビューの提供 書き込み権限の管理 行レベル、カラムレベルの権限管理 参考:http://postd.cc/using-cqrs-with-event-sourcing/ →書き込み・読み込みでモデルを分けたのなら、ORMも分けても良いのでは??
  29. 29. データストア・ORMの組み合わせ 単一物理モデル 複数物理モデル 単一ORM 複数ORM データストア ORM × データストア構成、ORM構成は要件に応じて好きに組み合わせて良い
  30. 30. アジェンダ ● CQRSがなぜ必要か ○ CQRSとは何か ○ なぜ必要か ● CQRSのCommandとQueryでそれぞれどのORMを使うか ○ ORMのパターン ○ 今回の選定基準 ● CQRSをDDDにどう組み込むか ○ DDDとは何か ○ どのようなレイヤー構成になるか ○ CQRSをどう組み込むか ○ コードサンプル ● 所感・まとめ
  31. 31. ORMのパターン 中心 SQLロジックの組み込み方法 代表的プロダクト SQL中心 XMLなどの設定ファイルで Javaの外に保持する MyBatis、SQL view、 ベンダー特有のストアドプロシージャ 文字列としてJavaに組み込む JDBC、JPAネイティブクエリ 内部DSL(ドメイン固有言語)で Javaロジックに組み込む jOOQ, Criteria API (JPQL) オブジェクト 中心 オブジェクトリレーショナルマッピングもしく はアクティブレコードを通じて Javaに組み 込む JPAとその実装(Hibernate etc) コレクションAPI中 心 固有のコレクションAPIとしてJavaにSQLロ ジックを組み込む Speedment、JINQ、Slick (Scala)、LINQ (.NET) 参考:https://www.infoq.com/jp/news/2017/02/data-geekery-releases-jooq-3-9
  32. 32. メリット デメリット Spring Data JPA (Hibernate) オブジェクト中心なので、モデルに振る 舞いを持たせやすい Hibernateが裏でいろいろやりすぎ、仕様が 理解しにくくすぐ詰まる、 N+1問題へのケア、 子テーブルたどる階層の制限  etc.. MyBatis SQLを直接かけるのでシンプル、安心 SQLがテキスト記述なのでタイプセーフでは ない、XMLとかに設定を書くのは今時結構辛 い jOOQ タイプセーフなDSLで書きやすい、読み やすい オブジェクトリレーションの再現は弱い ORM選定 →書き込み系にHibernate、読み込み系にjOOQを採用
  33. 33. jOOQとは? ● DSL中心でクエリビルドできる ORM “jOOQ generates Java code from your database and lets you build type safe SQL queries through its fluent API.” ● 特徴 ○ DatabaseFirst:  DBスキーマからJavaファイルを生成 ○ Typesafe SQL:  コンパイルが通れば確実に DBカラムタイプセーフ  IDE補完も協力 ○ Code Generation:  gradleでコード生成、flywayとの相性バツグン ○ 同一アプリケーションで Hibernateなどとの併用も可能 (トランザクションも同じトランザクションマネージャーを共有できる )
  34. 34. レイヤ設計 ● ORMの選定 ○ 書き込み:Hibernate ○ 読み込み:jOOQ ● これをどうやってアーキテクチャに組み込むか? →DDDのレイヤ階層設計を使用する
  35. 35. アジェンダ ● CQRSがなぜ必要か ○ CQRSとは何か ○ なぜ必要か ● CQRSのCommandとQueryでそれぞれどのORMを使うか ○ ORMのパターン ○ 今回の選定基準 ● CQRSをDDDにどう組み込むか ○ DDDとは何か ○ どのようなレイヤー構成になるか ○ CQRSをどう組み込むか ○ コードサンプル ● 所感・まとめ
  36. 36. DDDとは ● Domain Driven Design(ドメイン駆動設計)の略称 2003年にEric・Evansが提唱したソフトウェア開発の設計手法 ● Implementing Domain-driven Design の出版が2013年 ● ドメインとは ○ 「アプリケーションの中心となる業務領域」のこと ● 原則 ○ ドメインとドメインロジックを中心に設計する ( ≠ データモデル中心) ○ 複雑なロジックをドメインモデルに寄せる (オブジェクト志向に則る ) ○ ドメインエキスパート (業務の専門家)と継続的にコミュニケーションし、モデルを改善し続ける ● メリット ○ ステークホルダー間のコミュニケーションが容易になる ○ ソースの可読性、変更容易性、メンテナンス性が高まる
  37. 37. DDDとは ● カバー範囲が広く、ネットの記事を見るとこれら混同しがちな記事が多い ● 戦略的DDD ○ 開発プロセス、思想 ■ ユビキタス言語、コアドメイン、汎用ドメイン ○ アーキテクチャ設計 ■ レイヤー化アーキテクチャ ■ 境界付けられたコンテキスト ● 戦術的DDD ○ モジュール設計 ■ モデル駆動設計 (オブジェクトに振る舞いを持たせる ) ■ エンティティ/リポジトリなどのデザインパターン → 今回は「レイヤー化アーキテクチャー」の部分を中心に CQRSと繋げる
  38. 38. DDDのレイヤー化アーキテクチャー ● DDDのレイヤー化アーキテクチャの代表的なもの ○ ヘキサゴナルアーキテクチャー ○ オニオンアーキテクチャー ○ クリーンアーキテクチャー ● 目指すものは基本的に同じ、用語と責務の割り当て方が少し異なるだけ ● 今回はオニオンアーキテクチャーを選定 (用語、責務分割がわかりやすかったため) オニオンアーキテクチャー参考記事 https://dzone.com/articles/onion-architecture-is-interesting
  39. 39. →インフラ層の変更がすべてのレイヤーに影響を及ぼしてしまう  (使用ライブラリの変更、データベースの変更など) オニオンアーキテクチャーのエッセンス ● 従来のレイヤー化アーキテクチャー ○ すべてのレイヤーが間接的にインフラ層に依存 RDB Accessor dynamo Accessor Business Logic Data User Interface
  40. 40. 依存関係逆転の原則 ● 「依存関係逆転の原則」を使用して前述の問題を解決する ● Infrastructure層に記述していた処理のIFだけをDomain層、AppServie層に定義 ● InfraStructure層ではそのIFをimplementsする ● Domain層、AppSerice層は特定のライブラリに依存しない記述になり、 あとからInfraStrucure実装を差し替えても影響がなくなる(理論上は) <<iterface>> DB Accessor RDB Accessor Impl dynamo Accessor Impl
  41. 41. DDD、CQRS ● ここでReadModelとWriteModelを分離
  42. 42. アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <<iterface>> Repository Entity Repository Impl <<iterface>> Query Serivce DTO Query Serivce Impl Hibernate jOOQ
  43. 43. アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <<iterface>> Repository Entity Repository Impl <<iterface>> Query Serivce DTO Query Serivce Impl
  44. 44. サンプルコード Domain Model(entity, repository) Entity Repository
  45. 45. アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <<iterface>> Repository Entity Repository Impl <<iterface>> Query Serivce DTO Query Serivce Impl
  46. 46. サンプルコード Application Service ApplicationService(新規作成) ApplicationServiceの引数 ApplicationServiceの戻り値
  47. 47. サンプルコード Application Service ApplicationService(更新)
  48. 48. ①entityが保持する値はUserのIdでLong型だが、 引数をUserにすることでTypeSafeになる (関係ないLong型の値が設定されることがない ) ②引数と関係なくステータスコードを設定 →オブジェクト生成時には必ず "未完了"の状態でインスタ ンス生成される、という「不変条件」を課している ③「完了」「未完了」という状態を、中でどのような値で表現 しているかを完全に隠蔽できている また、publicに公開していないメソッド以外では、インスタン スの値を更新できないことが明示されている ① ② ③ ポイント
  49. 49. アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <<iterface>> Repository Entity Repository Impl <<iterface>> Query Serivce DTO Query Serivce Impl
  50. 50. サンプルコード queryService ApplicationService(検索) 引数 クエリモデルのサービス IF 引数 戻り値
  51. 51. アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <<iterface>> Repository Entity Repository Impl <<iterface>> Query Serivce DTO Query Serivce Impl jOOQ
  52. 52. サンプルコード queryService クエリモデルのサービス実装クラス
  53. 53. サンプルコード 複雑なクエリ 引数 クエリモデルのサービス実装クラス
  54. 54. テスト戦略 Application Service Domain Model Query Model User Interface Infrastructure ● テストはアプリケーション層のメソッドに対して書く ● メリット: ○ ルールがわかりやすい ○ Domainモデルは振る舞いを組み合わせたテストを書 かないと品質向上に繋がりにくい ○ 内部のリファクタがしやすい ○ AppServiceの呼び元がリアルタイム APIだろうが非同 期のwatcher処理だろうが安心 ○ ● デメリット: ○ テストでのDB接続が必要になる
  55. 55. アジェンダ ● CQRSがなぜ必要か ○ CQRSとは何か ○ なぜ必要か ● CQRSのCommandとQueryでそれぞれどのORMを使うか ○ ORMのパターン ○ 今回の選定基準 ● CQRSをDDDにどう組み込むか ○ DDDとは何か ○ どのようなレイヤー構成になるか ○ CQRSをどう組み込むか ○ コードサンプル ● 所感・まとめ
  56. 56. 導入所感 ● クエリの書きやすさ、検索条件の拡張性は快適 ● Hibernateでハマることを最小限に減らせてよかった  commandでも一部ハマったので、これが参照系でもやっていたら。。 ● DDDとSpring Data JPAの相性は抜群  RepositoryをIFだけ書けば実装クラスを作ってくれるのはとても楽 ● Applicationレイヤでのテストは書きやすく、リファクタもしやすい ● ORMを分けなかったとしても、基本的な思想としてCQ分離は意義がある
  57. 57. 拡張・疑問 ● 要件として参照と更新を同時に行う必要がある場合は? 例:ある情報を参照したら参照ログを残したい ○ 対策1: ApplicationServiceを複数呼び出すメソッドを書く ○ 対策2: ドメインイベントを発行してWatcherで拾い、非同期的に更新を行う ● アプリケーションサービスはコマンド系・クエリ系でクラスを分ける? ○ 分けたほうが使用するモジュールにに間違いないことが判別しやすい。 でもどちらも可
  58. 58. 拡張・疑問 ● Queryモデルの参照にApplicationを挟む必要があるのか?   (もしくは、ApplicationServiceにクエリサービスがあればよいのでは?) ○ 操作ログインの情報によって分岐 (操作ユーザーに紐づく情報を取得、操作ユー ザーの権限によって表示情報を制御など) したい時のために、業務処理を行うク ラスを挟んでおきたい ● テストのDBはどのように? ○ 実DBMSはMySQL、テストはH2で動かしている
  59. 59. ありがとうございました

×
Save this presentationTap To Close