はじめに
ドメイン駆動設計でモデリング〜設計〜サンプルプログラム・テストコード作成
までを一通りやってみての気づきや重要ポイントをまとめました。
今回はリポジトリにフォーカスしてまとめています。
本記事ではドメイン駆動設計のモデリング結果をベースに作業しています。
モデリングについてはこちらを参照ください。
ドメイン駆動設計のモデリング練習|データベーススペシャリストの過去問を題材に
そもそもリポジトリと集約とは
支払情報やユーザ情報など、システムで取り扱う情報はたいてい
データベースなどに登録(永続化)してあとから取り出せる(再構築)必要があります。
リポジトリ(保管庫)とはこの永続化、再構築を行う役割を担います。
リポジトリにこの永続化、再構築の責務を持たせることで、
データベースの具体的な操作(DBと接続する、接続失敗したときのハンドリング、SQL発行など)を
ユースケース層(アプリケーションサービス)などから切り離すことができ、
データベースの具体的な操作が1箇所に集まり、
プログラムの見通しも良くなります。
この部分が
ここから切り離されて、
ユースケース層が本質的な処理だけ書けば良くなり、
ごちゃごちゃせず見通しがよくなります。
また、
リポジトリパターンではインタフェースをきって、実装と切り離しています。
これによって、
依存関係をコントロールしてアプリケーションを柔軟にすることができます。
例えば今回のソースコードでは、
PaymentCreateUseCaseがPaymentRepositoryを利用(依存)しています。
そして、PaymentRepositoryImplがPaymentRepositoryを実装(依存)しています。
これによって、
PaymentRepositoryImplがもしSQL Serverのデータベースの具体的な操作をガリガリ書いてて、
postgreSQLにRDBMSを切り替えるとなったときにはPaymentRepositoryを実装する、
別のPaymentRepositoryPostgres的なモノを作れば
PaymentCreateUseCaseや他のPaymentRepositoryに依存しているクラスに全く影響が出ません。
このように依存関係をコントロールすること(依存性逆転の原則)で、
アプリケーションをデータベースの具体的な操作に依存しない
柔軟なものにできます。
さらに、
PaymentRepositoryMemory的なモノを作って
DBと接続せずにメモリ上でデータの永続化をしてテストするといった使い方もできます。
ここまでがリポジトリについてでした。
一方、集約は本でこのように述べられていました。
集約
必ずひとまとまりで永続化する、強い整合性を持ったエンティティ/値オブジェクトの単位
ドメイン駆動設計 サンプルコード&FAQ(松岡 幸一郎)
例えデータベース上でのテーブルの持ち方が分かれていても
必ずセットで永続化、再構築したい単位が集約です。
必ずセットなので、同一トランザクションで登録をすることになり、
大きすぎる集約にしてしまうとロックを広い範囲でかけることになるので
集約の単位には注意が必要。
リポジトリのサンプルコードとテストコード
まずは、作成したリポジトリのサンプルコードと
リポジトリのテストコードのサンプルについてポイントに絞って紹介します。
前回のモデリング編で事前に支払集約で集約の範囲を決めているので、
今回はそれをベースに、
集約をそのままDBに登録したり、DBから取得したりできるリポジトリを作成しました。
集約の範囲としては支払登録時に支払明細と支払方法明細を必ずセットで扱うようにしました。
リポジトリとJOOQなどのORマッパーとエンティティ/値オブジェクトとの関係とリポジトリの役目を絵にすると
こんなイメージです。
リポジトリでDBに登録するイメージ
リポジトリでDBから取得するイメージ
リポジトリはテーブルレイアウトをJavaの世界で表現するRecordとエンティティや値オブジェクトとの変換作業と、
実際にクエリを発行してDBデータ登録、データ取得を行う2つの役割を持たせています。
支払リポジトリのインタフェース(ドメイン層)〜ドメイン駆動設計サンプルコード〜
エンティティを渡すとDBへ登録する登録用のメソッドと、
key情報を渡すとDBからデータを取得してエンティティを返却するメソッドを用意しています。
支払リポジトリの実装(インフラ層)〜ドメイン駆動設計サンプルコード〜
実際にJOOQ(ORマッパー)でDBとやりとりしているところです。
特に登録の方は1つのエンティティを3つのテーブルに登録しにいくので、
@Transactionalで1トランザクションとしてどれかのテーブルへの登録がコケたら、
ロールバックできるようにしています。
また、Record→エンティティ、エンティティ→Recordの変換用メソッドをそれぞれ用意しています。
支払リポジトリのテストコード〜ドメイン駆動設計サンプルコード〜
DBと繋いでテストをしています。
@Transactionalでテストが終わったらロールバックできるようにしてInsertをなかったことにしています。
登録してその後、登録した内容が取得できるかのテストを
登録と取得のテストを一気に実施しています。
(この一気にテストするやつは松岡さんの本で紹介されていたのですが、
個人的には効率よくて真似させてもらっています)
ローカルで立てたDBと繋ぐので事前テーブル作ったり云々はFlywayで行う想定です。
諸々Flyway周りについては以下にまとめています。
flywayでテストデータ投入|spring bootで作成したAPIのテストコードのためのデータ準備
また、今回作成したサンプルコードはgithub上に公開しており、
前提となる開発環境構築手順などもまとめてあるので興味がある方は参考にしてみてください。
IntelliJ IDEA、docker desktop(postgreSQL、keycloak)、Flyway、DBeaverを利用したバックエンド開発環境構築
リポジトリのサンプルコードを作成してみた感想や気付き
まず、モデリングの時のように手が止まることはなかったです。
その前にモデリングで集約の範囲を決めるときにまぁまぁ手は止まったのですが、、
実際に手を動かして実装してみて改めて感じましたが、ドメインモデル図があると
ドメインモデル図→ソースコードへ落とし込む際にほとんど迷うことがなかったです。
(複雑なビジネスロジックになるとそうはいかないかもしれませんが、、)
現場で今までよく見かけていた、実装がほぼ終わって
ドメインエキスパートから違うと言われて作り直してまた設計。。。みたいなのが、
ドメインエキスパートを巻き込みながらモデリングの段階で
ドメインモデル図の粒度でフィードバック受けつつブラッシュアップする。
モデリング結果からスムーズに実装に落とすことができる。
一部考慮が漏れてたとしても、
ある程度ブラッシュアップできているので大きくひっくり返る可能性も低く、
修正もモデリング結果をスムーズに実装に落とし込む仕掛けができているから早い。
現場で導入するとそんなイメージかなと感じました。
一方、その素晴らしい仕組みを作るためには
リポジトリの詰め替え作業やエンティティ作って値オブジェクト作って、リポジトリ作って・・・
などはその分最初に頑張る必要があるなと実感しました。
Orikaみたいなマッパーライブラリを利用したら楽かなとも少し思ったのですが、
RecordとEntityで完全自動でマッピングできないところは自分でマッピング設定するので、
自分で詰め替えるのとあまり変わらなかったり、
これは本にも書いてありましたが、
ライブラリがリフレクターなどでごちゃごちゃやるので実行して初めてバグに気付くので、
自分で詰め替えを最初に頑張る。
という方針が先々のことを考えると良さそうに感じました。
性能やメモリ使用率などなどについては、今まで僕が携わっていたシステムと比べて、
インスタンスの作成や詰め替えなどなど入るので
どうなのかは個人的に興味がありますが。。。どうなんだろう。
とはいえ、そんなとてつもない性能の要求がでるケースばかりではないでしょうし、
メリットを活かせるケースは自分の過去を振り返っても十分あります。
おまけ
ドメイン駆動設計でモデリング〜コーディング〜テストコード作成
までを実際に行ってみてのまとめ記事を作ったので興味がある方はご覧ください。
ドメイン駆動設計でモデリング〜サンプルプログラム・テストコード作成(まとめ記事)
また、
動画でもDDDを現場で実践したり既存の設計を改善した話をしているので
興味がある方は是非参考にしてみてください!!