Slide 1

Slide 1 text

JJUG CCC 2017 fall DDD x CQRS - 更新系と参照系で異なるORMを併用して上手くいった話 2017/11/18 株式会社ビズリーチ 松岡 幸一郎

Slide 2

Slide 2 text

● 松岡 幸一郎 ● 株式会社ビズリーチ ● @little_hand_s ● #ccc_m4 発表者紹介

Slide 3

Slide 3 text

最近こんなCMしてる会社です 自己紹介

Slide 4

Slide 4 text

Java 技術遍歴 SIer時代

Slide 5

Slide 5 text

Java 技術遍歴 SIer時代 Excel

Slide 6

Slide 6 text

技術遍歴 Excelはもういい

Slide 7

Slide 7 text

Java 技術遍歴 SIer時代 Excel Spring Boot DDD・CQRS Vue.js 現職

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

ブログで細々とDDD布教中 http://little-hands.hatenablog.com/

Slide 11

Slide 11 text

なんですが

Slide 12

Slide 12 text

DDD x CQRS 更新系と参照系で 異なるORMを併用して 上手くいった話 テーマ ←今日のメイン

Slide 13

Slide 13 text

DDDとは ● Domain Driven Design(ドメイン駆動設計)の略称 ● ドメインとは ○ 「アプリケーションの中心となる業務領域」のこと ● 原則 ○ ドメインとドメインロジックを中心に設計する ( ≠ データモデル中心) ○ 複雑なロジックをドメインモデルに寄せる (オブジェクト志向に則る) ○ ドメインエキスパート(業務の専門家)と継続的にコミュニケーションし、モデルを改善し続ける ● メリット ○ ステークホルダー間のコミュニケーションが容易になる ○ ソースの可読性、変更容易性、メンテナンス性が高まる ● EricEvansによる定義

Slide 14

Slide 14 text

DDDとは ● Domain Driven Design(ドメイン駆動設計)の略称 ● ドメインとは ○ 「アプリケーションの中心となる業務領域」のこと ● 原則 ○ ドメインとドメインロジックを中心に設計する ( ≠ データモデル中心) ○ 複雑なロジックをドメインモデルに寄せる (オブジェクト志向に則る) ○ ドメインエキスパート(業務の専門家)と継続的にコミュニケーションし、モデルを改善し続ける ● メリット ○ ステークホルダー間のコミュニケーションが容易になる ○ ソースの可読性、変更容易性、メンテナンス性が高まる ● EricEvansによる定義 略

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

まず、 CQRSに関する誤解を解きたい テーマ

Slide 17

Slide 17 text

まず、 CQRSに関する誤解を解きたい テーマ (※個人の見解です)

Slide 18

Slide 18 text

CQRSは イベントソーシングと セットで行う必要がある テーマ

Slide 19

Slide 19 text

CQRSは イベントソーシングと セットで行う必要がある テーマ

Slide 20

Slide 20 text

CQRSは イベントソーシングと セットで行う必要がある テーマ CQRSは単独で適用できます

Slide 21

Slide 21 text

CQRSとは

Slide 22

Slide 22 text

● CQRSとは ○ Command Query Responsibility Segregation コマンドクエリ責務分離 ○ 書き込み用のモデルと読み込み用のモデルを分ける設計パターン CQRSとは

Slide 23

Slide 23 text

CQRSの提唱者 ● Greg Young ○ DDD + CQRS + イベントソーシングを推している そのため、CQRSとイベントソーシングがセットで語られがち → 本質的には別のもの

Slide 24

Slide 24 text

● なぜモデルを分ける必要があるのか? ● モデルを分けるとはどういうことなのか? → 背景から説明します CQRS背景

Slide 25

Slide 25 text

● 一般的なシステムでは、書き込みと読み込みの両方が、 単一のデータストアで同じモデルを使用する ● テーブルに対応したオブジェクトがあり、それを使用してCRUD操作する CQRS背景 DB Data Model User Interface Application IF IF

Slide 26

Slide 26 text

● システムが大きくなるほど、 ○ 書き込み:制御は複雑になっていく ○ 読み込み:複数テーブルの情報をまとめて加工する必要性が高まっていく → 一つのモデルに関する処理がどんどん複雑になっていく CQRSが必要な背景

Slide 27

Slide 27 text

● システムが大きくなるほど、 ○ 書き込み:制御は複雑になっていく ○ 読み込み:複数テーブルの情報をまとめて加工する必要性が高まっていく → 一つのモデルに関する処理がどんどん複雑になっていく CQRSが必要な背景 ● そもそも、書き込み・読み込みで要件が大きく異なる ○ 整合性か、速度か ○ オブジェクトの形か、結合や集計した形か(モデル表現の違い) ○ トラフィック数は圧倒的に読み込み処理が多い ○ パフォーマンス要件は異なることが多い   → どこかにしわ寄せ、妥協が発生する

Slide 28

Slide 28 text

CQRSのステップ

Slide 29

Slide 29 text

CQRSのステップ ● 段階的CQRS a. 書き込み / 読み込みモデルを分離する b. 書き込み / 読み込みデータストアを分離する c. イベントソーシングと統合する  → 要件を見極めてどうするか判断すればよい

Slide 30

Slide 30 text

CQRS - 1.単一物理データストアモデル ● 書き込みと読み込みのモデルを別物として用意する DB Data Model User Interface Application IF IF

Slide 31

Slide 31 text

CQRS - 1.単一物理データストアモデル ● 書き込みと読み込みのモデルを別物として用意する DB Data Model User Interface Application IF IF DB User Interface IF IF Write Model Read Model

Slide 32

Slide 32 text

CQRS - 1.単一物理データストアモデル ● 書き込みと読み込みのモデルを別物として用意する ● 書き込みモデル: テーブル毎に対応したエンティティ等 ● 読み込みモデル: テーブルをJoinした結果、SQL viewの取得結果1行などを1モデルとする  → それぞれ適したモデルを扱えるので、処理効率が良い書き方ができる   コードがシンプルになる DB Data Model User Interface Application IF IF DB User Interface IF IF Write Model Read Model

Slide 33

Slide 33 text

CQRS - 2.複数物理データストアモデル ● 書き込みと読み込みのデータストアを物理的に分離する DB User Interface IF IF Write Model Read Model

Slide 34

Slide 34 text

CQRS - 2.複数物理データストアモデル ● 書き込みと読み込みのデータストアを物理的に分離する DB User Interface IF IF Write Model Read Model Write Data Store Read Data Store User Interface IF IF Write Model Read Model

Slide 35

Slide 35 text

CQRS - 2.複数物理データストアモデル ● 書き込みと読み込みのデータストアを物理的に分離する ● Read Data Store ○ シンプルなのはread-onlyのレプリカ ○ 全く別の機構を選択することも可能 (Readをelastic searchにするなど)  → 参照/更新のストア分離により、それぞれの負荷に合わせたスケーリングが可能   異なるアーキテクチャのデータストアを利用可能 DB User Interface IF IF Write Model Read Model Write Data Store Read Data Store User Interface IF IF Write Model Read Model

Slide 36

Slide 36 text

● すべてのアクションを「イベント」として記録する設計パターン 既存のデータのupdateは一切しない ○ Writeモデルは「イベント」として記録される ○ Readモデルはそこから特定の形に変形される MaterializedViewや物理的なデータを別途生成する、など CQRS - 3.イベントソーシング (@little_hand_s から参考資料たどれます「イベントソーシングの参考資料」 )

Slide 37

Slide 37 text

Write/Readで ORMを分ける

Slide 38

Slide 38 text

分離のメリット・デメリット メリット デメリット 1.モデル分離 処理効率が良い書き方ができる コードがシンプルになる readモデルにwriteモデルの制約を 効かせられなくなる 2.データソース 分離 Read / Writeを分離してスケールさせること ができる 設計の幅が広がる データ同期の仕組構築 /メンテコストが必要 3.イベント ソーシング データ追跡しやすい ミューテーション排除によるバグ抑止 インピーダンスミスマッチとの決別 導入難易度がが一気に上がる 導入・教育コストが高い

Slide 39

Slide 39 text

分離のメリット・デメリット メリット デメリット 1.モデル分離 処理効率が良い書き方ができる コードがシンプルになる readモデルにwriteモデルの制約を 効かせられなくなる 2.データソース 分離 Read / Writeを分離してスケールさせること ができる 設計の幅が広がる データ同期の仕組構築 /メンテコストが必要 3.イベント ソーシング データ追跡しやすい ミューテーション排除によるバグ抑止 インピーダンスミスマッチとの決別 導入難易度がが一気に上がる 導入・教育コストが高い → ORMも適正に合わせて使い分ければ、このメリットをより大きくできるのではないか?

Slide 40

Slide 40 text

CQRSのオプション 各オプションは要件に応じて好きに組み合わせて良い 単一物理ストア 複数物理ストア データストア Read / Write 異なるモデル Read / Write 同じモデル モデル × しない する イベント ソーシング × 単一ORM 複数ORM ORM ×

Slide 41

Slide 41 text

CQRSのオプション 各オプションは要件に応じて好きに組み合わせて良い 単一物理ストア 複数物理ストア データストア Read / Write 異なるモデル Read / Write 同じモデル モデル × しない する イベント ソーシング × → メリット/デメリット、コストを考慮し、今回はこのような構成を選択 単一ORM 複数ORM ORM ×

Slide 42

Slide 42 text

Write/Read ORMの要件 ● Write Model ORM ○ オブジェクト志向でモデルに振る舞いをもたせたい ・DDDの思想を反映 ○ テーブルに対応したオブジェクト

Slide 43

Slide 43 text

Write/Read ORMの要件 ● Write Model ORM ○ オブジェクト志向でモデルに振る舞いをもたせたい ・DDDの思想を反映 ○ テーブルに対応したオブジェクト ● Read Model ORM ○ 複数テーブルをjoinしたり、集計したりしたい ○ 効率の良いクエリが書きたい ○ 実行されるクエリを制御したい

Slide 44

Slide 44 text

Write/Read ORMの要件 ● Write Model ORM ○ オブジェクト志向でモデルに振る舞いをもたせたい ・DDDの思想を反映 ○ テーブルに対応したオブジェクト ● Read Model ORM ○ 複数テーブルをjoinしたり、集計したりしたい ○ 効率の良いクエリが書きたい ○ 実行されるクエリを制御したい  → この要件に合うようにORMを選定する

Slide 45

Slide 45 text

ORMのパターン 中心 SQLロジックの組み込み方法 代表的プロダクト オブジェクト 中心 オブジェクトリレーショナルマッピングを通じ てJavaに組み込む Hibernate、(ActiveRecord) SQL中心 Java外 - XMLなどの設定ファイル MyBatis、SQL view、 ベンダー特有のストアドプロシージャ Java内 - String文字列 JDBC、JPAネイティブクエリ Java内 - 独自DSL jOOQ、JPQL

Slide 46

Slide 46 text

ORMのパターン 中心 SQLロジックの組み込み方法 代表的プロダクト オブジェクト 中心 オブジェクトリレーショナルマッピングを通じ てJavaに組み込む Hibernate、(ActiveRecord) SQL中心 Java外 - XMLなどの設定ファイル MyBatis、SQL view、 ベンダー特有のストアドプロシージャ Java内 - String文字列 JDBC、JPAネイティブクエリ Java内 - 独自DSL jOOQ、JPQL  → 先述の要件を踏まえ、3つのORMでメリット/デメリット検討

Slide 47

Slide 47 text

jOOQとは ● DBスキーマからDBアクセス用オブジェクト生成 ● DSL中心でクエリビルド、実行できるORM ● かなりSQLに近い書き方、かつタイプセーフにSQLが書ける

Slide 48

Slide 48 text

メリット デメリット Hibernate (Spring Data JPA) モデルに振る舞いを持たせやすい SpringDataJPAがDDDを想定した仕様 仕様の理解しにくさ、キャッシュなどのトラブ ル、想定外の挙動 etc.. MyBatis SQLを直接かけるのでシンプル、安心 SQLがテキスト記述、 XMLに設定を書くのは今時結構辛い jOOQ タイプセーフなDSLで書きやすい、 読みやすい オブジェクトリレーションの表現に 制限あり ORM選定

Slide 49

Slide 49 text

メリット デメリット Hibernate (Spring Data JPA) モデルに振る舞いを持たせやすい SpringDataJPAがDDDを想定した仕様 仕様の理解しにくさ、キャッシュなどのトラブ ル、想定外の挙動 etc.. MyBatis SQLを直接かけるのでシンプル、安心 SQLがテキスト記述、 XMLに設定を書くのは今時結構辛い jOOQ タイプセーフなDSLで書きやすい、 読みやすい オブジェクトリレーションの表現に 制限あり ORM選定 →Writer Model ORMにHibernate、Read Model ORMにjOOQを採用

Slide 50

Slide 50 text

CQRSを適用するための アーキテクチャ

Slide 51

Slide 51 text

● 一般的な3層アーキテクチャの問題 ○ Write/Readのモデル使用範囲を明確に分けられない ○ BusinessLogic層がFatになりやすい アーキテクチャ Business Logic Data User Interface Infrastructure

Slide 52

Slide 52 text

● 業務ロジックをドメイン層に凝集する ● アプリケーションサービス層はドメイン層が許可した操作を必要に応じて呼ぶ ● この辺りはDDDの思想に基づいた設計 アーキテクチャ Business Logic Data User Interface Infrastructure Application Service Domain Model User Interface Infrastructure (@little_hand_s から参考資料たどれます 「ドメイン駆動 + オニオンアーキテクチャ概略」 )

Slide 53

Slide 53 text

interface アーキテクチャ Application Service Domain Model User Interface Infrastructure Application Service Domain Model User Interface Infrastructure 実装クラス ● ドメイン層をPOJOにするために依存関係を逆転する ● ドメイン層が公開したIFに対してインフラ層が実装する設計

Slide 54

Slide 54 text

● ドメイン層をWrite Model (ドメインモデル)とRead Modelに分割する ● インフラ層の実装クラスをWriteとReadで異なるORMで実装する アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure Application Service Domain Model User Interface Infrastructure interface 実装クラス

Slide 55

Slide 55 text

アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <> Repository Entity Repository Impl <> Query Serivce DTO Query Serivce Impl

Slide 56

Slide 56 text

Spring Data JPA アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <> Repository Entity Repository Impl <> Query Serivce DTO Query Serivce Impl Hibernate Hibernate(アノテーションだけ)

Slide 57

Slide 57 text

Spring Data JPA アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <> Repository Entity Repository Impl <> Query Serivce DTO Query Serivce Impl jOOQ Hibernate Hibernate(アノテーションだけ)

Slide 58

Slide 58 text

サンプルコード

Slide 59

Slide 59 text

サンプルコード Task タスクID タスク名 ステータス User ユーザーID ユーザー名 作成者 担当者 ● シンプルなタスク管理アプリケーションを想定 ● タスクの制約 ○ 作成時の制約 ■ 「作成者」と「担当者」として実在のユーザーを持つ ■ 常に「未完了」状態で作成 ○ 変更時の制約 ■ 名前は変更できない ■ 完了/未完了の制御だけできる

Slide 60

Slide 60 text

アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <> Repository Entity Repository Impl <> Query Serivce DTO Query Serivce Impl Hibernate(アノテーションだけ)

Slide 61

Slide 61 text

サンプルコード Domain Model(entity, repository) Entity Repository

Slide 62

Slide 62 text

ポイント ①entityが保持する値はUserのIdでLong型だが、 引数をUserにすることでTypeSafeになる ②必ず"未完了"の状態でインスタンス生成、  という制約をコンストラクタで表現 ③ステータス更新用のメソッドは公開されているが、 nameは更新用メソッドを公開しないことにより 「名前を変更できない」という制約を表現 ① ② ③ コンストラクタと公開メソッドで制約を表現 コンストラクタ 状態遷移用メソッド (@little_hand_s から参考資料たどれます 「モデルでドメイン知識を表現するとは何か」 )

Slide 63

Slide 63 text

サンプルコード Application Service ApplicationService(新規作成) ApplicationServiceの引数 ApplicationServiceの戻り値

Slide 64

Slide 64 text

サンプルコード Application Service ApplicationService(更新)

Slide 65

Slide 65 text

アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <> Repository Entity Repository Impl <> Query Serivce DTO Query Serivce Impl

Slide 66

Slide 66 text

ポイント ・POJOで引数と戻り値の関係だけ定義 →「ライブラリ何使うかは任せるけど、 この条件で指定したらこういう形で返してね」 という宣言をしている サンプルコード queryService クエリモデルのサービス IF 引数

Slide 67

Slide 67 text

アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <> Repository Entity Repository Impl <> Query Serivce DTO Query Serivce Impl jOOQ

Slide 68

Slide 68 text

サンプルコード queryServiceImpl その他のクエリ部分 ①クエリライクにJavaコードでselect, from, join条件を書いている ②resultをfetch後に戻り値の方に mapしたものをreturnしている ③条件指定の方法は次のページ ① ② ③

Slide 69

Slide 69 text

・引数オブジェクトの値をjOOQのConditionオブジェクトにマッピング ・引数オブジェクトのパラメータを取得するメソッドを Optionalで返せば、  値があるときだけ条件指定、ということも可能 ・引数のパラメータを増やせば検索の拡張も簡単 サンプルコード queryServiceImpl 条件指定部分

Slide 70

Slide 70 text

Spring Data JPA アーキテクチャ Application Service Domain Model Query Model User Interface Infrastructure <> Repository Entity Repository Impl <> Query Serivce DTO Query Serivce Impl jOOQ Hibernate Hibernate(アノテーションだけ)

Slide 71

Slide 71 text

導入結果

Slide 72

Slide 72 text

導入結果 ● 実際どうだった?

Slide 73

Slide 73 text

導入結果 ● 実際どうだった? →かなり使い勝手が良いです!!

Slide 74

Slide 74 text

導入結果 ● 実際どうだった? →かなり使い勝手が良いです!! ● Read Model ○ Hibernateの得意な部分は活かしつつ、 参照系ではまりがちなHibernateの使い方に悩む時間を一切カット (クエリを書いた通りに動く) ○ 検索条件の拡張も快適

Slide 75

Slide 75 text

● 実際どうだった? →かなり使い勝手が良いです!! ● Read Model ○ Hibernateの得意な部分は活かしつつ、 参照系ではまりがちなHibernateの使い方に悩む時間を一切カット (クエリを書いた通りに動く) ○ 検索条件の拡張も快適 ● Write Model ○ DDDとSpring Data JPAの相性は抜群  RepositoryのIFだけ書けば実装クラスを作ってくれるのはとても楽 ○ writeモデルに振る舞いを凝縮している安心感、読みやすさ◎ 導入結果

Slide 76

Slide 76 text

Q&A

Slide 77

Slide 77 text

Q&A ● そもそも2つのORM同時に使っていいの?

Slide 78

Slide 78 text

Q&A ● そもそも2つのORM同時に使っていいの? →前述の通りjOOQ公式で紹介されている使い方 (@little_hand_s から参考資料たどれます 「jOOQ関連リンク」)

Slide 79

Slide 79 text

Q&A ● テストはどうしている?

Slide 80

Slide 80 text

Q&A ● テストはアプリケーション層のメソッドに対して書く (ちなみにspock) ● メリット: ○ 費用対効果が良い ○ 内部のリファクタがしやすい ○ アプリケーションサービス層で担保すれば、 呼び出し元がAPIだろうが画面だろうが watcherだろう が安心感がある ● デメリット: ○ テストでのDB実行環境構築が必要 Application Service Domain Model Query Model User Interface Infrastructure ● テストはどうしている?

Slide 81

Slide 81 text

Q&A ● 要件として参照と更新を同時に行う必要がある場合は? 例:ある情報を参照したら参照ログを残したい

Slide 82

Slide 82 text

Q&A ● 要件として参照と更新を同時に行う必要がある場合は? 例:ある情報を参照したら参照ログを残したい ● 対策1: ApplicationServiceを複数呼び出す処理を書く ファサードのようなレイヤなど。 通常のApplicationServiceとは区別するのがよい ● 対策2: ドメインイベントを発行してWatcherで拾い、非同期的に更新を行う

Slide 83

Slide 83 text

Q&A ● アプリケーションサービスはコマンド系・クエリ系でクラスを分ける? Application Service Domain Model Query Model User Interface Infrastructure

Slide 84

Slide 84 text

Q&A ● アプリケーションサービスはコマンド系・クエリ系でクラスを分ける? → 分けたほうが使用するモジュールにに間違いないことが判別しやすい。 でもどちらも可 Application Service Domain Model Query Model User Interface Infrastructure

Slide 85

Slide 85 text

● QueryServiceはUIから直接参照させてもよいのでは? Q&A Application Service Domain Model Query Model User Interface Infrastructure <> Query Serivce DTO Query Serivce Impl

Slide 86

Slide 86 text

● QueryServiceはUIから直接参照させてもよいのでは? →どちらでも可、要件次第。  今回はApplicationServiceを挟んでよかった。 ○ テスト単位がApplicationServiceというレイヤで統一できる ○ 単純な条件抽出以外の処理を分岐したい場合、 その制御をApplicationServiceに任せられる ■ 操作ユーザーの情報に応じて 処理を分岐 ■ 権限制御、抽出条件変更など Q&A Application Service Domain Model Query Model User Interface Infrastructure <> Query Serivce DTO Query Serivce Impl

Slide 87

Slide 87 text

まとめ

Slide 88

Slide 88 text

まとめ ● CQRSは要件に応じて柔軟にやり方を変えられる ● Write/Readモデルを分けるときにORMを分けるのは、 モデルを分けるメリットを大きくできる ● ぜひご検討してみてください ● ご意見、ご質問は @little_hand_s もしくは#ccc_m4 まで

Slide 89

Slide 89 text

ありがとうございました