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