概要
-
SpringBoot
で3層レイヤー + ドメインロジックを実装したサンプルコードを例に、DDDを具体的なコードベースでどう実現したかを説明していただいた。 -
実践ドメイン駆動設計を読んでDDDを理解しようとしていたけど、具体的な実装を丸っと見て説明を聞いて、本の内容の概念的な話を実現しようとするとこういう形になるのか、と納得した点も多かったので投稿。
導入
- ソフトウェアの複雑さって結局のところビジネスルールの複雑さだよね。
- ビジネスルールを「計算モデル」として扱い、型志向で実装する。
- 関心の分離:「計算モデル」モジュールと「計算モデルのインプットを準備する」モジュールを明確に分離する。これが大事。
- この時点ではよくわからなかった。
サンプル実装から学ぶ各レイヤ
サンプル実装は時給ベースの給与計算システム
実際の現場では以下の順に設計実装を行なっている
ドメイン層
↓
アプリケーション層
↓
データソース層
↓
プレゼン層
ドメイン層
-
給与計算の計算モデルを62個のクラスで定義
-
Payroll
クラスはContract
とAttendance
を組み合わせて計算を行うモデル。これがValue Object
で、これがEntity
ですみたいな説明とかはなかった。注目すべきはそこではないってことだと感じた。 -
model
パッケージにはEntity
(計算モデルと呼んでいた)を格納、type
パッケージにはValue Object
を格納。明示的にパッケージを分けているのは計算モデルがぼんやりとしてしまうのを避けたかったから。 -
バリデーションには
BeanValidation
を使う。これは計算モデルの仕様として有効桁数や有効範囲を文書化できるというメリットがある。 -
計算モデルはビジネスルールの仕様を外部に公開するもの。
getter/setter
,lombok
,JPA
などはビジネスルールとして意味をなすものではないから使わない。 -
計算モデルでは基本的にプリミティブ型をなるべく潰していき方針で実装する。これはその値が何を表しているのかを設計情報としてソースコードから読み取れる様にするという意味で非常に重要。
-
getter
を作らないということにこだわるというよりは、getter
を使うことによってビジネスルールの設計情報が曖昧になることを気にしていた。内容的にはgetter
だが何を返すか、という点がわかるメソッド名にしていた。
ドメイン層で感じたこと
計算モデルを型として扱うこと、という原則に忠実だった。これに関しては、ビジネスルールとして公開したい内容をソースコードで表すということにこだわっている感じだった。これのメリットはサンプル実装のソースコードを読んだだけで、一目瞭然だった。ビジネスルールの登場人物がどういう振る舞いをするのか、その登場人物が他の登場人物とどういう関係なのか、そのクラスを見るだけでわかる。設計書はいらない。
「まるで文書の様に」というコードの書き方にこだわって、実験を繰り返しているらしい。
アプリケーション層
- 計算モデルを生成する役割。計算モデルの計算結果(計算モデル自身の場合が多い)をデータソース層に登録したり、プレゼンテーション層に渡したりする役割。
- サンプルでは
Service
とCordinator
があった。どちらもSpring
の@Service
アノテーションを付与したクラス。 -
Service
には一つのRepository
をAutowire
する。これによってソースコード上で、何の計算モデルを扱うかが明確になる。 -
Cordinator
は複数のService
をAutowire
する。Repository
は扱わない。 -
Service
もCordinator
も繰り返し、分岐を持たない。複雑さを排除して薄く保っている。
アプリケーション層で感じたこと
この辺はドメイン層でほとんどロジックを吸収できているからこそ、薄く保てているんだと思う。それの証拠にアプリケーション層にはビジネスルールに関するロジックはほぼない。アプリケーション層にビジネスルールを表す何かがあったとしたら、それはドメインモデルを見直すべきだと感じた。
データソース層
- O/Rマッパーには
Mybatis
を使ってる。javaではインターフェースを実装するだけ。実際のマッピング、SQLはxml
で書いていく。 - 計算モデルとテーブル設計は互いに影響を与えたくない。だからこのマッピングを自動化してしまう選択をすると、お互いに予期せぬ制約を負うことになる。だからSQLというツールを使ってそこのマッピングを制御できる様にするという選択をとった。
- イミュータブルデータモデル方式を採用している。UPDATE文は一切なし。
- UPDATE文を使う時に計算モデルに複雑さが生まれてくるんだろうなあって思ったけど、これは正直メリットをあまり理解できなかった。。
- 外部サービスとのデータ変換などもこの層で行う。
json
をゴリゴリ変換していくみたいなごちゃっとした処理はデータのシステムの入り口or出口でやる。 - データソース層で発生したエラーはユーザに返してもどうしようもないものがほとんどだから、ドメインでハンドリングするとかはしていない。ドメイン層でバリデーションとかはしておかないと、データソース層のエラーをハンドリングする羽目にはなるが、そうしない様に頑張ることが重要。
データソース層で感じたこと
計算モデルとデータオブジェクトのマッピングを制御するというのは重要なことに思えた。JPAを使用すると複雑なクエリ条件だとjava側の実装が発生する。そこにはビジネスロジックとしての価値はないため、DBに対してどういう操作を行うかということを外出しすることでjava側のロジックをドメイン関心事に留めることができる。
この辺りは気にしておくべきだなと感じた。
全体を通して
- プリミティブ型をなるべく撲滅する。これはDDD(or オブジェクト指向)の文脈でよく言われますが、これを実際のコードベースで見て、説明を受けてメリットを理解した。これはソースコードの表現力を高める強力な原則であり、javaでコーディングをするメリットだと思う。
- ソースコードを文書の様に、というこだわりを何度も強調された。これを目指すことのメリットはとても大きい様に感じた。ソースコードを読むだけでそのドメインの実現したいビジネスルールが理解できる。サンプルコードのデータベースのテーブル、スキーマ名は日本語にするという実験的アプローチもあった。そういう部分へのこだわりは強く持つべきだなと思う。
- サンプルコードについて、何度も何度も自分たちの実装を見直してきたと、説明していた。DDDの導入は一度満足のいく実装にたどり着いたら終わりではなく、そこから継続的に俯瞰し、依存を整理し、実験し、検証していくといったプロセスを繰り返して品質を向上させていくプロセスだと再認識した。