2022.05.20
STAFF BLOG
スタッフブログ
TECHNICAL
テクログ
どうも!いまだにGW気分で仕事に取り組んでるわいです!
今回はGW中に往復のフェリーで2周した「ドメイン駆動設計 モデリング/実装ガイド」について書いてみたいと思います。
1周目読んだ時の感想は、「今までの自分の考えはすべて間違っていた。しかし、もう大丈夫!DDDを理解したぞ!」でした。
2周目読んだ時の感想は、「一周目の理解はすべて間違っていた。しかし、もう大丈夫!DDDを理解したぞ!」でした。
正直、DDDのことをわかってあげられるなんてのはまだまだ先の話です。
しかし、この本を読んだことでDDDがどんなものであるかという雰囲気は少し知ることができました。
以下、自分なりの発見・理解を挙げていきます。(間違った理解もあるかと思うので、鵜呑みにしないでください)
モデリング
- ドメインの問題を解決するためのモデルをユースケース図やドメインモデル図などを用いてモデリングする
- 極力モデルとコードの表現を近づけるために、モデル表現をUIやDBから隔離するようなアーキテクチャを採用する
- モデルに関する言葉を開発側とビジネス側で統一して、認識齟齬が生まれないようにする
- 最初からモデルは完成せず、徐々に改善していくもの
- 継続的にリファクタリングするために、テストコードの存在が重要
- 運用して得られた知見を逐一モデルとコードに反映していく
- 「必ず守りたい強い整合性を持ったオブジェクトのまとまり」を集約という
- 強い整合性確保が必要なものを1つの集約にする
- トランザクションを必ず1つにする(必ずまとめて更新する)
- 例)部の承認状態が部員の数によって変更する場合、「部」と「部員」は1つの集約として定義する(当たり前だが同じ「部」と「部員」でもモデリング次第で集約は変わる)
- オブジェクト同士の関連は集約の内外で2種類に分かれる
- 集約内の参照はインスタンス参照
- 親のオブジェクト取得時に同時に子も取得して、インスタンスでアクセスする
- 集約外の参照はID参照
- IDを指定してDBから取得する
- 集約内の参照はインスタンス参照
ドメイン層
- 対応するオブジェクトにドメインモデルの知識を書く
- ドメインに対するルール・制約が1つのクラスを読めばわかる
- 常に正しいインスタンスしか存在させない
- すべてのインスタンスはコンストラクタ、もしくはファクトリメソッドを経由して生成する
- 不用意に public な setter を作らず、インスタンス更新用メソッドはルール・制約に基づいたもののみ公開する
- DBから取ってきた値は、専用のコンストラクタを設け、全ての属性をバリデーションなしで受け取る
- ドメインモデルを表現する「エンティティ」と「値オブジェクト」の違いは同一判定の方法
- エンティティの例は「社員」などが考えられ、この場合同姓同名の人は存在しうる。エンティティは社員番号などの識別子を用いて同一判定をおこなう
- 値オブジェクトの例は「お金」などが考えられ、これらの同一判定は保持する値でおこなう
- ただ、コインコレクターにとっては同じ10円玉でもそれらを区別して扱いたいということがおこりうる。この場合はエンティティとしてモデリングする
- 「モデルをオブジェクトとして表現すると無理があるもの(例えば、集合に対する操作)」の表現はドメインサービスを使う
- 例)メールアドレスを更新する際の重複チェック。さすがに1つのユーザオブジェクト自身がチェックするのは無理がある
- ただし、極力エンティティと値オブジェクトで実装するようにして、どうしても避けられない時にのみドメインサービスを使うようにする
- 手続き的になるので、従来の”サービスクラス”のようにファットなクラスになりやすい
- リポジトリ(集約単位で永続化層へのアクセスを提供するもの)は List のように扱う
- 「ユーザを登録する」「ユーザを退会状態にする」ではなく「新規登録(退会)状態のユーザを add する」という使い方にする
- 削除時はID指定で問題ない(わざわざエンティティを取得して、それを渡す必要はない)
- リポジトリではドメインに対するルール・制約を持たないようにする
- 「ユーザを登録する」「ユーザを退会状態にする」ではなく「新規登録(退会)状態のユーザを add する」という使い方にする
- ORM をドメイン層のクラスとして使ってはいけない
- すべての項目に setter があるため、更新処理に制限をかけられなくなる
- テーブルとドメインモデルが切り離せなくなる
- 使うとしてもインフラ層のリポジトリ実装クラス内に閉じ込める
ユースケース層
- ユースケース層からプレゼンテーション層へ返す値の型は2種類考えられる
- 専用の戻り値クラス(Data Transfer Object など)に詰め替えて返す
- ドメイン層のクラス(ドメインオブジェクト)をそのまま返す
- 長期的に保守性を高めるためには、1. のほうが有利
- メリット
- ドメインオブジェクトに、プレゼンテーションに関連する処理(表示用のフォーマット処理など)が混入するのを防げる
- ドメイン層の修正の影響を、プレゼンテーション層が直接受けなくなる
- デメリット
- ドメインオブジェクトからの詰め替えのコストが発生する
- メリット
- 一覧画面で複数の集約の情報を表示したいような参照系の処理をリポジトリだけで実現しようとすると問題が起こる
- 複数の集約からドメインオブジェクトを取得して DTO に詰め替える処理が、ループが増えて読みにくいコードになる
- 画面に返す必要のないデータも一緒に取得するのでパフォーマンスが悪化する
- 複数集約の条件で絞り込んでのページングができない
- これらの問題を解決するために、CQRS(コマンドクエリ責任分離)は有用
- 参照用のモデルと更新用のモデルを分離するというアーキテクチャ
- 一覧画面などの特定のユースケースに特化した値の型(ex. DTO)を用意する
- ユースケース層にその DTO を返すクエリサービスのインターフェイスを定義して、インフラ層でそれを実装する
プレゼンテーション層
- いわゆるコントローラ
- JSONを受け取ってJSONを返したり、フォームの入力を受け取ってHTMLを返したりする
- フォーマット処理などの表示に関するメソッドや、ユースケース層から受け取った型に対応したコンバーターはここに定義する
さいごに
箇条書きになって申し訳ないですが、自分の発見をたくさん紹介したかったのでこのような形にしました。
今回紹介した内容は、あくまで”私の発見”であり、各層の依存関係などについては既知のものとして進めましたが、本書ではそれらについてもきちんと説明されてあり、またより発展的なことも書かれていますので、少しでも興味があればぜひ手に取って読んでみてください!
DDDのことはまだまだわかってあげられないですが、本書を通して少しは気持ちを察せられるようになったかなと思います。
以上、わいでした。
健闘を祈る!!