(旧版)ddd × cqrs 更新系と参照系で異なるormを併用して上手くいった話

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

Upload: dcubeio

Post on 21-Jan-2018

5.045 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

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

2017/09/19株式会社ビズリーチ

松岡 幸一郎

Page 2: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

発表者紹介

● 松岡 幸一郎

Page 3: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

主にサーバーサイド

Page 4: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

フロントは最近入門

Page 5: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

プロジェクト経歴

銀行システム4年 若手向け転職サイト2年 社内システム半年

Page 6: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

主な技術

ExcelJava

Page 7: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

Excelはもういい

Page 8: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

主な技術トピック

ExcelJava

Page 9: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

開発楽しい!

主な技術トピック

ExcelJava

Page 10: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

しかし待ち受ける技術的負債との戦い

Page 11: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

主な技術トピック

ExcelJava SAStrutsjQuery

Java

Page 12: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

万全の対策

Page 13: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

主な技術トピック

ExcelJava SAStrutsjQuery

Spring BootDDDVue.js

Java

Page 14: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

DDD x CQRS

更新系と参照系で

異なるORMを併用して

上手くいった話

テーマ

Page 15: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

CQRSDDDORM

に興味がある方

導入検討している方

想定対象者

Page 16: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アーキテクチャ系の話あるある

Page 17: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

抽象的な話がひたすら続いてよくわからない。。

Page 18: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

● 今日のゴール:このアーキテクチャの説明をすること

アーキテクチャ

ApplicationService

DomainModel

QueryModel

User Interface Infrastructure

Business Logic

Data

User Interface

一般的な3層アーキテクチャ DDD, CQRSを組み込んだアーキテクチャ

Page 19: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

● 今日のゴール:このアーキテクチャの説明をすること

アーキテクチャ

ApplicationService

DomainModel

QueryModel

User Interface Infrastructure

<<iterface>>Repository

Entity

Repository Impl

<<iterface>>Query Serivce

DTO

Query SerivceImpl

Hibernate

jOOQ

Page 20: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アジェンダ

● CQRSがなぜ必要か

○ CQRSとは何か

○ なぜ必要か

● CQRSのCommandとQueryでそれぞれどのORMを使うか

○ ORMのパターン

○ 今回の選定基準

● CQRSをDDDにどう組み込むか

○ DDDとは何か

○ どのようなレイヤー構成になるか

○ CQRSをどう組み込むか

○ コードサンプル

● 所感・まとめ

Page 21: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アジェンダ

● CQRSがなぜ必要か

○ CQRSとは何か

○ なぜ必要か

● CQRSのCommandとQueryでそれぞれどのORMを使うか

○ ORMのパターン

○ 今回の選定基準

● CQRSをDDDにどう組み込むか

○ DDDとは何か

○ どのようなレイヤー構成になるか

○ CQRSをどう組み込むか

○ コードサンプル

● 所感・まとめ

Page 22: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

CQRSとは?

● Command and Query Responsibility Segregation

(コマンド・クエリ責務分離)

● 2010年 Greg Young氏が考案したパターン

○ クエリ: 結果のみを返し、状態の変更は行わない

○ コマンド: 状態の変更のみを行い、結果は返さない

○ 「 これは、質問をすることで回答を変化させてはならないということだ。」

● ネット上の文献は、英語で探した方がまとまった記事があるのでオススメ

○ https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs

Page 23: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

● 従来のシステムでは、コマンド(データの書き込み)とクエリ(データの読み込み)

の両方が、単一のデータストア内の同じモデルを使用して実行される

● scaffoldのような自動生成の仕組みで作成したモデルが使用されることも

CQRS概要

Page 24: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

CQRSが必要な背景

● 書き込み・読み込みで要件が大きく異なる

● 書き込み、読み込みで適した表現は必ずしも一致しない

● 同時にセキュリティ制御をしようとすると複雑に

● パフォーマンス要件は異なることが多い

書き込み 読み込み

データの整合性維持 データの検索と抽出の効率化

アトミックな更新・トランザクション 導出値(合計など)の計算

バージョン管理 複数のビューの提供

書き込み権限の管理 行レベル、カラムレベルの権限管理

全リクエストの内占める割合は小さい 全リクエストの内占める割合は大きい

参考:http://postd.cc/using-cqrs-with-event-sourcing/

Page 25: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

CQRS - 単一物理データストアモデル

● 書き込みと読み込みのIFを別物として用意する

● 書き込み・読み込みでモデルを分離することも可能(ただし必須ではない)● 参照モデルは、SQL viewの作成、任意のクエリ発行などによる

Write IFRead IF

Page 26: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

CQRS - 複数物理データストアモデル

● 書き込みと読み込みのデータストアを物理的に分離する

● 参照ストアはread-onlyのレプリカや、全く別の機構を選択することも可能

(参照系はelastic searchにするなど)● 参照/更新のストア分離により、それぞれの負荷に合わせたスケーリングが可能

● セキュリティ制御も個別に制御がしやすくなる

Page 27: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

CQRS - イベントソーシング

● さらに進めるとイベントソーシングの話が出てきますが、

今回は割愛。

● 参考

https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing

Page 28: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

再掲:書き込み・読み込みの要件の違い

書き込み 読み込み

データの整合性維持 データの検索と抽出の効率化

アトミックな更新・トランザクション 導出値(合計など)の計算

バージョン管理 複数のビューの提供

書き込み権限の管理 行レベル、カラムレベルの権限管理

参考:http://postd.cc/using-cqrs-with-event-sourcing/

→書き込み・読み込みでモデルを分けたのなら、ORMも分けても良いのでは??

Page 29: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

データストア・ORMの組み合わせ

単一物理モデル

複数物理モデル

単一ORM

複数ORM

データストア ORM

×

データストア構成、ORM構成は要件に応じて好きに組み合わせて良い

Page 30: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アジェンダ

● CQRSがなぜ必要か

○ CQRSとは何か

○ なぜ必要か

● CQRSのCommandとQueryでそれぞれどのORMを使うか

○ ORMのパターン

○ 今回の選定基準

● CQRSをDDDにどう組み込むか

○ DDDとは何か

○ どのようなレイヤー構成になるか

○ CQRSをどう組み込むか

○ コードサンプル

● 所感・まとめ

Page 31: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

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

Page 32: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

メリット デメリット

Spring Data JPA(Hibernate)

オブジェクト中心なので、モデルに振る舞いを持たせやすい

Hibernateが裏でいろいろやりすぎ、仕様が

理解しにくくすぐ詰まる、N+1問題へのケア、

子テーブルたどる階層の制限  etc..

MyBatis SQLを直接かけるのでシンプル、安心 SQLがテキスト記述なのでタイプセーフではない、XMLとかに設定を書くのは今時結構辛い

jOOQ タイプセーフなDSLで書きやすい、読みやすい

オブジェクトリレーションの再現は弱い

ORM選定

→書き込み系にHibernate、読み込み系に jOOQを採用

Page 33: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

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などとの併用も可能

(トランザクションも同じトランザクションマネージャーを共有できる )

Page 34: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

レイヤ設計

● ORMの選定

○ 書き込み:Hibernate○ 読み込み:jOOQ

● これをどうやってアーキテクチャに組み込むか?

→DDDのレイヤ階層設計を使用する

Page 35: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アジェンダ

● CQRSがなぜ必要か

○ CQRSとは何か

○ なぜ必要か

● CQRSのCommandとQueryでそれぞれどのORMを使うか

○ ORMのパターン

○ 今回の選定基準

● CQRSをDDDにどう組み込むか

○ DDDとは何か

○ どのようなレイヤー構成になるか

○ CQRSをどう組み込むか

○ コードサンプル

● 所感・まとめ

Page 36: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

DDDとは

● Domain Driven Design(ドメイン駆動設計 )の略称

2003年にEric・Evansが提唱したソフトウェア開発の設計手法

● Implementing Domain-driven Design の出版が2013年

● ドメインとは

○ 「アプリケーションの中心となる業務領域」のこと

● 原則

○ ドメインとドメインロジックを中心に設計する ( ≠ データモデル中心 )○ 複雑なロジックをドメインモデルに寄せる (オブジェクト志向に則る )○ ドメインエキスパート (業務の専門家 )と継続的にコミュニケーションし、モデルを改善し続ける

● メリット

○ ステークホルダー間のコミュニケーションが容易になる

○ ソースの可読性、変更容易性、メンテナンス性が高まる

Page 37: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

DDDとは

● カバー範囲が広く、ネットの記事を見るとこれら混同しがちな記事が多い

● 戦略的DDD○ 開発プロセス、思想

■ ユビキタス言語、コアドメイン、汎用ドメイン

○ アーキテクチャ設計

■ レイヤー化アーキテクチャ

■ 境界付けられたコンテキスト

● 戦術的DDD○ モジュール設計

■ モデル駆動設計 (オブジェクトに振る舞いを持たせる )■ エンティティ /リポジトリなどのデザインパターン

→ 今回は「レイヤー化アーキテクチャー」の部分を中心に CQRSと繋げる

Page 38: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

DDDのレイヤー化アーキテクチャー

● DDDのレイヤー化アーキテクチャの代表的なもの

○ ヘキサゴナルアーキテクチャー

○ オニオンアーキテクチャー

○ クリーンアーキテクチャー

● 目指すものは基本的に同じ、用語と責務の割り当て方が少し異なるだけ

● 今回はオニオンアーキテクチャーを選定

(用語、責務分割がわかりやすかったため)

オニオンアーキテクチャー参考記事

https://dzone.com/articles/onion-architecture-is-interesting

Page 39: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

→インフラ層の変更がすべてのレイヤーに影響を及ぼしてしまう (使用ライブラリの変更、データベースの変更など)

オニオンアーキテクチャーのエッセンス

● 従来のレイヤー化アーキテクチャー

○ すべてのレイヤーが間接的にインフラ層に依存

RDB Accessor

dynamo Accessor

Business Logic

Data

User Interface

Page 40: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

依存関係逆転の原則

● 「依存関係逆転の原則」を使用して前述の問題を解決する

● Infrastructure層に記述していた処理のIFだけをDomain層、AppServie層に定義

● InfraStructure層ではそのIFをimplementsする

● Domain層、AppSerice層は特定のライブラリに依存しない記述になり、

あとからInfraStrucure実装を差し替えても影響がなくなる(理論上は)

<<iterface>>DB Accessor

RDB Accessor Impl

dynamo Accessor Impl

Page 41: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

DDD、CQRS● ここでReadModelとWriteModelを分離

Page 42: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アーキテクチャ

ApplicationService

DomainModel

QueryModel

User Interface Infrastructure

<<iterface>>Repository

Entity

Repository Impl

<<iterface>>Query Serivce

DTO

Query SerivceImpl

Hibernate

jOOQ

Page 43: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アーキテクチャ

ApplicationService

DomainModel

QueryModel

User Interface Infrastructure

<<iterface>>Repository

Entity

Repository Impl

<<iterface>>Query Serivce

DTO

Query SerivceImpl

Page 44: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

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

Repository

Page 45: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アーキテクチャ

ApplicationService

DomainModel

QueryModel

User Interface Infrastructure

<<iterface>>Repository

Entity

Repository Impl

<<iterface>>Query Serivce

DTO

Query SerivceImpl

Page 46: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

サンプルコード Application ServiceApplicationService(新規作成)

ApplicationServiceの引数 ApplicationServiceの戻り値

Page 47: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

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

Page 48: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

①entityが保持する値はUserのIdでLong型だが、

引数をUserにすることでTypeSafeになる

(関係ないLong型の値が設定されることがない )

②引数と関係なくステータスコードを設定

→オブジェクト生成時には必ず "未完了"の状態でインスタ

ンス生成される、という「不変条件」を課している

③「完了」「未完了」という状態を、中でどのような値で表現

しているかを完全に隠蔽できている

また、publicに公開していないメソッド以外では、インスタン

スの値を更新できないことが明示されている

ポイント

Page 49: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アーキテクチャ

ApplicationService

DomainModel

QueryModel

User Interface Infrastructure

<<iterface>>Repository

Entity

Repository Impl

<<iterface>>Query Serivce

DTO

Query SerivceImpl

Page 50: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

サンプルコード queryServiceApplicationService(検索)

引数

クエリモデルのサービス IF

引数

戻り値

Page 51: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アーキテクチャ

ApplicationService

DomainModel

QueryModel

User Interface Infrastructure

<<iterface>>Repository

Entity

Repository Impl

<<iterface>>Query Serivce

DTO

Query SerivceImpl jOOQ

Page 52: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

サンプルコード queryServiceクエリモデルのサービス実装クラス

Page 53: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

サンプルコード 複雑なクエリ

引数

クエリモデルのサービス実装クラス

Page 54: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

テスト戦略

ApplicationService

DomainModel

QueryModel

User Interface Infrastructure

● テストはアプリケーション層のメソッドに対して書く

● メリット:○ ルールがわかりやすい○ Domainモデルは振る舞いを組み合わせたテストを書

かないと品質向上に繋がりにくい○ 内部のリファクタがしやすい○ AppServiceの呼び元がリアルタイムAPIだろうが非同

期のwatcher処理だろうが安心○

● デメリット:○ テストでのDB接続が必要になる

Page 55: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

アジェンダ

● CQRSがなぜ必要か

○ CQRSとは何か

○ なぜ必要か

● CQRSのCommandとQueryでそれぞれどのORMを使うか

○ ORMのパターン

○ 今回の選定基準

● CQRSをDDDにどう組み込むか

○ DDDとは何か

○ どのようなレイヤー構成になるか

○ CQRSをどう組み込むか

○ コードサンプル

● 所感・まとめ

Page 56: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

導入所感

● クエリの書きやすさ、検索条件の拡張性は快適

● Hibernateでハマることを最小限に減らせてよかった

 commandでも一部ハマったので、これが参照系でもやっていたら。。

● DDDとSpring Data JPAの相性は抜群

 RepositoryをIFだけ書けば実装クラスを作ってくれるのはとても楽

● Applicationレイヤでのテストは書きやすく、リファクタもしやすい

● ORMを分けなかったとしても、基本的な思想としてCQ分離は意義がある

Page 57: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

拡張・疑問

● 要件として参照と更新を同時に行う必要がある場合は?

例:ある情報を参照したら参照ログを残したい

○ 対策1:ApplicationServiceを複数呼び出すメソッドを書く

○ 対策2:ドメインイベントを発行してWatcherで拾い、非同期的に更新を行う

● アプリケーションサービスはコマンド系・クエリ系でクラスを分ける?

○ 分けたほうが使用するモジュールにに間違いないことが判別しやすい。

でもどちらも可

Page 58: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

拡張・疑問

● Queryモデルの参照にApplicationを挟む必要があるのか?

  (もしくは、ApplicationServiceにクエリサービスがあればよいのでは?)○ 操作ログインの情報によって分岐 (操作ユーザーに紐づく情報を取得、操作ユー

ザーの権限によって表示情報を制御など) したい時のために、業務処理を行うク

ラスを挟んでおきたい

● テストのDBはどのように?

○ 実DBMSはMySQL、テストはH2で動かしている

Page 59: (旧版)DDD × CQRS   更新系と参照系で異なるORMを併用して上手くいった話

ありがとうございました