はじめに
「ドメイン駆動設計入門」という本を読んだので軽くまとめようと思い記事にしている。
動機
動機としては今関わっているバックエンドプロジェクトが DDD 思想のアーキテクチャで書かれおり、コードを書く上で困ることが多々あるので、少しでも解消されることを目的として読んだ。
元々の理解度としては、「ドメイン駆動設計 モデリング/実装ガイド」というDDDの初歩本やその周辺の軽めの本、関連の記事は読んでいたのでなんとなくわかるという感じ。
感想
結論として、この本を読んで DDD の解像度は上がった。しかし、使いこなせるかというとそうでもない気がしており結局タイトル通り入門書の立ち位置ではある。
DDD を実装するにあたる思想、アーキテクチャが書かれておりモデリングについては当書の範囲ではない。
本の構成としてはドメインオブジェクトの説明から始まり、アプリケーションを作っていく流れに従ってアンチパターンを指摘しつつ、新しいオブジェクト(レイヤー)を紹介してくれるので読みやすいと思う。
特に DDD 系の本はいきなり同心円の図が出てきて体調が悪くなりがちなのだが、この本は最後の方に例の図が出てくるほどボトムアップな解説でそういう面でも入門者向けの本なのだろう。
大枠はこの本通りに書けば良いのだろうがどうしても集約によってはドメインが薄くなってしまったり、ただの検索結果をドメインオブジェクトに突っ込む疑念への答えはなかった。
後は DDD の都合上、ちゃんとしたビジネスで使われているドメインが一般に公開されることもないので実際のサービスを作る上での疑問点の解消方がネットに出ていないというのも難しい。そう言ったものは当書や他の DDD 本を辞書にしつつ自分なりの解釈を作りながら戦うしかないのだろう(半諦め)。
そうはいってもそれぞれのオブジェクトの責務やどのような背景から責務を分けたくなるかはどのアーキテクチャでも考えるべきことなので読んでおいて損はない。
気になったとこまとめ
値オブジェクト
値にロジックを持たせたり、その値に不正な値を存在させない時に使う。
「値オブジェクト」と「エンティティ」の違いがわかるだろうか?ここの理解があやふやだと取り扱いを間違えるので明確に定義する必要がある。
値の定義
- 不変である
- 交換が可能である
- 等価性によって比較される
二つ目と三つ目はおまけで本質は不変性にあると思う。
値は変更されないことを保証されていて欲しい。2だと思っていた値が関数に渡した時や数十行のコードの中でしれっと100に変わっていたら致命的な問題が起きる。
つまり、値オブジェクトは一度作るとその値は変わらないことが保証されている。
「交換が可能」な点は代入によってのみ値の変更が可能であるということ。ややこしいから無視。
「等価性」はあくまでもオブジェクトの中で「値」を表現しているのでオブジェクトの比較演算では比較ができない。かといって、毎回次のように値を取り出していてはロジックが分散するので「比較ロジックを持とうね」ということ。
// NG
ValuObject1.Name === ValueObject2.Name
// OK
ValueObject1.Equal(ValueObject2)
エンティティ
エンティティはidによって識別される持つオブジェクトである。以下の特徴を持っている
- 可変
- 同じ属性であってもidで区別される
こちらも値オブジェクトと同様に比較メソッドがあるわけだが、idによって比較される。
よくある間違いとしてゲッターやセッターを生やしただけのデータの入れ物になっているエンティティがあるが、アンチパターンである。ドメインは外部に漏らしてはいけないのでゲッターやセッターを外部が要求しているのは不穏な臭いがする。ドメインを表現することに集中せよ。
余談だが、外部(ユースケース)からエンティティ内の値オブジェクトのメソッドは呼び出してはいけない。当たり前だが、エンティティの振る舞いとして呼び出す必要がある。
もっと厳密にいうと集約ルートのみが直接操作して良い。
ドメインサービス
値オブジェクトやエンティティが自身の状態や振る舞いを持っていたのと比較してドメインサービスは自身についての振る舞いを持たない。
例えばユーザーの重複処理など、ユーザーエンティティが自身に重複を確認するメソッドを生やすのが不自然な時に外部から呼び出せる責務を与えたものである。
極論、エスケープハッチ的な存在であり、ここになんでも入れることができる。ドメインモデル貧血症の原因にもなるので取扱注意⚠️
ドメインサービスに DB 操作をやらせても良いかという議論があるが著者は肯定的であった。
当然、入出力操作はアプリケーションの操作であってドメインに副作用があるのは良くない、しかし今回の例のように「ユーザーの重複」はドメインに関わっているなら別に良いという理屈らしい。
リポジトリ
インフラへの依存を抽象化する層。
ドメインオブジェクトを受け取ってDBに保存するのみ。
変に引数を受け取ってロジックを持っていたら注意。
アプリケーションサービス
ドメインやリポジトリをインターフェースを通して操作する層。
よくある悩みに更新処理で項目ごとに別のユースケースを作るか一つのUCが複数を更新できるようにするかがあるがどちらも正解。当書ではコマンドオブジェクトによってUC層に突っ込む引数を操作するやり方が紹介されていた。
ドメインサービスと同様に自身の振る舞いを持たずに活動や行動を表す。
トランザクション
可能な限り小さくする。
UIの責務
入力と表示が責務。ビジネスロジックが漏れ出してはいけない。
🤔ただ、formのinputの制約はvalue-object的なところは持った方がUX上がるくない?それは入力の責務に包括されている?
命名
ユビキタス言語をコードの表現として使う。
「名前を変更する」
x updateName
o changeName
実装詳細が漏れ出ているし、ドメインの会話で「変更する」を「更新する」に頭の中で置き換える必要がある。
境界付けられたコンテキスト
境界が異なるオブジェクトは同名で存在していても良い。
認証コンテキスト
User { email, password }
チームのユーザーコンテキスト
User { name, role }