ITエンジニア本大賞2021 で紹介されていた「ドメイン 駆動設計入門」(以下、本書と呼ぶ)が、DDDを学ぶ上でわかりやすかったです。一応他のDDD本も数冊読んではいたのですが、どうしてもユビキタス 言語や境界づけられたコンテキストなど”場合による”ものが頻出してしまい、結局どうすればいいんだ……となって手が動きにくかったのです。よくわからない概念の上にさらにわからない概念を積み重ねるので、どんどん混乱する感覚がありました。
反面、本書では副題の通りボトムアップ 形式を採っているため、実際にどう手を動かせば良いのか細かく確認できました。また不明点を最小化しながら進むだけでなく、その概念を導入することでどんな問題が解決されるのかが実例をもって明示されており、私のような独学派にはとてもありがたかったです。
さて本書ではC# を使っています。Java とたいして変わらないとはいえ細かいところは違ってきますので、ここでは付録で紹介されたソリューション(プロジェクト)構築方法をGradleユーザ向けに解釈したものを紹介します。なおウェブアプリケーション フレームワーク としてspring-bootを利用するものとしますが、他のフレームワーク でも大差ないはずです。
サブプロジェクトによる分割
Maven ではサブモジュール、Gradleではサブプロジェクトを使ってプロジェクトを分割します。本書ではプロジェクト分割の方針を2つ紹介していますが、ここでは簡単のためすべてのレイヤが固有サブプロジェクトを持つ構成を説明します。
domainサブプロジェクト には値オブジェクトやエンティティ、ドメイン サービス、仕様やリポジトリ インタフェースを配置します。ほぼPOJO で済むため、依存はspring-context
のようなアノテーション のみとなるでしょう。
ドメイン オブジェクトの振る舞いの多くがここに配置されるため、Javadoc やunit testを優先的に充実させる必要があるでしょう。また equals()
や hashCode()
を手書きするとコードやテストカバレッジ の見通しが悪くなることから、Immutables のようなコードジェネレータによって対策することも考えられます。
infrastrutureサブプロジェクト はdomainサブプロジェクトに含まれるリポジトリ インタフェースの実装を配置します。このサブプロジェクトはJDBC ドライバのような依存先ミドルウェア 固有の依存を持ちます。productionで利用する実装に加え、オンメモリで動作する実装も別途用意することで、unit testを書きやすくできます。
このサブプロジェクトではクラスをpublic
にする必要はありません。Springが自動的に@Repository
で修飾されたクラスを見つけてインスタンス を作るためです。他サブプロジェクトのunit testから呼び出す必要がある場合のみ、public
にすると良いでしょう。
applicationサブプロジェクト はdomainサブプロジェクトとinfrastructureサブプロジェクトの双方に依存します。アプリケーションサービスによるユースケース の記述をメインとするサブプロジェクトです。
本書の方針に従うならば、このサブプロジェクトに含まれるクラスやメソッドのAPI にはドメイン オブジェクトを露出させないことが望ましいと言えます。このため他サブプロジェクトには api
configuration ではなく implementation
configuration を使って依存します。これによりドメイン オブジェクトの露出をコンパイル エラーとして発見できる可能性が上がります。詳細は後述します。
presentationサブプロジェクト はapplicationサブプロジェクトにのみ依存します。後述するGradleの機能により、presentationサブプロジェクト内ではドメイン オブジェクトやRepositoryの実装に触れない状態を担保できます。MockMvc
などを使うことでunit testが重くなり、またこのサブプロジェクトをビルドしている際はGradleのworkerが空きがちなので、maxParallelForksオプション でテストを並列実行することの恩恵が大きいでしょう。
api とimplementationの使い分け
ここまでで2つ、詳細を後述すると述べたことがあります:
presentationサブプロジェクトに対するドメイン オブジェクトの露出をコンパイル エラーとして発見する方法
presentationサブプロジェクト内ではドメイン オブジェクトやRepositoryの実装に触れない状態を担保する方法
これらは表現こそ異なりますがひとつの課題を示しています。すなわち「ドメイン オブジェクトをpresentationサブプロジェクトに露出させたくない」です。
もちろん依存先のpublicフィールドや戻り値がドメイン オブジェクトでないことをArchUnit などで確認しても良いですが、Gradleならこれらをコンパイル エラーとして発見する方法があります。
API and implementation separation がこの課題に対する解決になります。これはAPI として外部に露出している依存と、そうではない内部利用している依存とを明確に分けて定義するものです。つまりサブプロジェクトが内部利用している依存は、そのサブプロジェクトの利用者に対して開示する必要はないという考えです。
これにより、applicationサブプロジェクトが依存しているdomainサブプロジェクトやinfrastructureサブプロジェクトを、presentationサブプロジェクトのコンパイル 時クラスパスに含めずプロジェクトをビルドすることができます。
依存に色を付ける必要があるという意味で手間は増えますが、そもそもAPIとして露出している依存は減らすべき(JLBP-2) という話もありますので”依存がAPI として露出しているのか?”を意識することは有用です。Gradle公式ドキュメント によるとコンパイル のパフォーマンス改善も期待できるケースがあるそうです。
例えば以上のサブプロジェクトの依存関係を図示すると、以下のようになります。
com.savvasdalkitsis.module-dependency -graph によって生成
api
configurationを使うのは1箇所だけです。infrastructureサブプロジェクトに含まれるクラスはdomainサブプロジェクトに含まれるリポジトリ インタフェースを実装しており、またドメイン オブジェクトをその引数や戻り値に使っているため、api
configurationを使って依存します。その他はすべて implementation
configurationが使えます。
補足: アセット生成を独立したサブプロジェクトの責務とする理由
前出の図に含まれているfrontendサブプロジェクト は、HTMLやJSなどのアセットを生成するためのものです。ドメイン 駆動からは外れますが解説します。
このサブプロジェクトだけ若干特殊で、npmやyarnのプロジェクトをfrontend-gradle-plugin を通じてビルドする作りになります。ReactやVueの開発をGradleで無理やり実現するのではなくnpmあるいはyarnを呼び出す形を取ることで、JavaScript 開発の知見を無理なく導入するとともにフロントエンド開発者にGradleやJava の学習を強要する必要性を減らせます。
ちなみにビルド性能の向上も期待できます。これはfrontend-gradle-pluginのタスク、特にinstallFrontendタスク が重いことと、Gradleは異なるプロジェクトに属するタスクだけparallel buildできる ことから、説明できます。なおプロジェクトと同数以上のworkerがいないと性能が出ないので、--max-workers
オプションを使うかorg.gradle.workers.max
プロパティ を使って明示的にワーカー数を引き上げておくことが望ましいです。以下に手元の環境でhyperfineした結果を貼っておきます:
Benchmark
Time ( mean ± σ ) : 28 .356 s ± 2 .624 s [ User: 1 .419 s, System: 0 .145 s]
Range ( min … max ) : 25 .914 s … 33 .624 s 10 runs
Benchmark
Time ( mean ± σ ) : 27 .161 s ± 3 .987 s [ User: 1 .388 s, System: 0 .137 s]
Range ( min … max ) : 23 .202 s … 33 .721 s 10 runs
Benchmark
Time ( mean ± σ ) : 24 .442 s ± 2 .916 s [ User: 1 .321 s, System: 0 .135 s]
Range ( min … max ) : 21 .799 s … 31 .388 s 10 runs
まとめ
「ドメイン 駆動設計入門」付録のGradle向け解釈を述べました。本書は私のような、ドメイン 駆動を手を動かしつつ独学したい方におすすめします。
本投稿が本書の内容を実践する上でGradleユーザの参考になれば幸いです。またGitHubに私の書いたプロジェクトを置いてあります ので、具体的にプロジェクト設定を見てみたい方は参照ください。