[読書] ドメイン駆動設計入門 -ボトムアップでわかる!ドメイン駆動設計の基本-
#ドメイン駆動設計
読んだ本
ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 成瀬 允宣 著 |
モチベーション
- (ずっと興味をもっていたんですが)満を持してドメイン駆動設計のスタディを始めました。
- 最初にドメイン駆動設計に足を踏み入れるときに、どの本を読もうかな〜と思っていろいろ探して、こちらに行き着きました。
- ボトムアップで、DDDで紹介されるパターンの実装を見ながらスタディできるということで、できるだけ具体的な内容で理解しやすいところから始められそう、という印象を受けて選んだ感じです。
かかった時間など
- 2022/9/27 〜 2022/11/18
- 26.5時間くらい
- 途中、8章でASP.netでのWebアプリケーションでの実装事例があってこれをDjangoに置き換えて読み替えるのに時間がかかりました。
- そのままC#でやるのであれば、もう少し早く終えられるものと思われます。
開発環境、進め方など
- サンプルコードは、C#でしたが、Pythonで書き直しながら進めました。
- C#を使わなかったので、いつもどおりM2 Macbook airで
- pyenv/pipenvで構築したPythonをVSCodeのパスに設定
- そのままpipでDjangoやpylintやmypyをインストールして、VSCode拡張にも適用して、コードを書きました。
- ただ、写経してステップバイステップでつくっていく本ではなかったので、あまり環境は気にしなくてよい感じでした。
ざっくりと理解したこと
紹介されているパターン
- 知識を表現するパターン
- Chapter2:値オブジェクト
- Chapter3:エンティティ
- Chapter4:ドメインサービス
- アプリケーションを実現するためのパターン
- Chapter5:リポジトリ
- Chapter6:アプリケーションサービス
- Chapter9:ファクトリ
- 知識を表現する、より発展的なパターン
- Chapter12:集約
- Chapter13:仕様
値オブジェクト
- 値オブジェクトについては、以下も参考
- くまぎさんの記事:Value Objectについて整理しよう
- fukabori.fm #73
- 上記の理解メモ
- 「すべての語彙を専用のクラスで包め」という行き過ぎた思想はさけるべき
- Primitive Obsessionの対策としての文脈でValue Objectという用語を使用するのも良くない
- エンティティの全メンバーやデータベースの全列に対して「顧客郵便番号」「送付先郵便番号」「事業所郵便番号」「契約日」などのクラス(メンバではなくクラス!)を定義して、immutableな振る舞いを強制する事を以てValue Objectとするのは、よくない。
- OOPとしてValidate処理をいれることと、プリミティブな値として扱えることを一緒くたにしないことが大事
- とはいえ、DDD文脈そして本書での値オブジェクトの理解
- クラスを増やすことに抵抗を感じる開発者は多く存在する。
- 値オブジェクトとして定義するほどの価値がある概念を実装中に発見したのであれば、それはドメインモデルとしてフィードバックするべき。
- 判断基準は、「そこにルールが存在しているか」、「それ単体で扱いたいか」
- ドメインモデルとして値オブジェクトを表現しておくメリット(値オブジェクトそのものよりもOOPとしてメリットっぽいけど)
- 表現力を増す
- 不正な値を存在させない
- 誤った代入を防ぐ
- ロジックの散財を防ぐ
エンティティ
- 等価性を持たず、オブジェクトごとに同一性(identity)によって区別されるものをドメインモデルとして表したもの
- 値オブジェクトなのかエンティティなのか捉え方で違うので、ドメインによって違うので、これをドメインエキスパートとコミュニケーションして見極める
ドメインサービス
- たしかにドメインモデルとして表現できるものだが、値オブジェクト、エンティティどちらにも記述するには、不自然な振る舞いを記述するもの
- 状態を持たない
リポジトリ
- データの永続化して再構築する処理を抽象的に扱うためのオブジェクト
- ドメイン知識ではない、データの永続化のために必要な技術的な知識(インフラストラクチャ)を分離するためのインタフェース
- オブジェクトを新しく作成する=ファクトリとは違い、あたかもオブジェクトがそれまで存在していたかのように扱えるようにする。
- リポジトリの実装を差し替えることでテストしやすさも担保できる。
- リポジトリは、リファクタリング第2版「9.5 値から参照への変更」でも出てきた。
アプリケーションサービス
- アプリケーションサービスはドメインオブジェクトが行うタスクの進行を管理し、問題解決に導く
- ドメインオブジェクトを組み合わせて実行するスクリプトのような振る舞い
- ドメインのルールは、ドメインオブジェクトに記述し、アプリケーションサービスには入れたくない。入れると同じようなコードを点在させることにつながる
凝集度
- 凝集度は、モジュールの責任範囲がどれだけ集中しているかを示すもの
- 凝集度が高いと、堅牢性、信頼性、再利用性、可読性が上がる
- 凝集度を評価する1つの方法に、LCOM(Lack of Cohesion in Methods)=すべてのインスタンス変数がすべてのメソッドで使われるようになっているかを計算する式がある
依存関係逆転の原則: Dependency Inversion Principle
- アプリケーションサービス側がリポジトリ(インフラストラクチャ)を利用するときに、インスタンス化するコードに、クラス名をハードコーディングすると柔軟性が下がる。
- これを解決する方法として、Service LocatorパターンとIoC(DI) Containerパターンがある
- Service Locatorパターン
- スタートアップスクリプトなどで事前に具体的なリポジトリのインスタンス名を登録しておく。
- ServiceLocatorに具体的なリポジトリの実装が登録されていないと行けないのがみえづらい。クラスの定義をみただけでこういう制限事項がわからないのは良い傾向ではない。
- ServiceLocatorへの登録が必要なインスタンスが増えたときに、これまでつくってきたServiceLocatorをつかったテストコードが動作しなくなり維持が難しい
- IoC(DI) Containerパターン
- ServiceLocatorと違い、serviceCollectionとserviceProviderを分離することで、依存関係が見えづらくなる問題を解決している
- ServiceLocator同様にIoCコンテナに対する設定は、スタートアップスクリプトにいれる
ファクトリ
- 複雑な道具は、その生成過程も得てして複雑である。
- ドメインオブジェクトに生成のための複雑なロジックをいれたくないので、ファクトリとして分離しておく。
- よくあるケースは、エンティティのインスタンス生成に関わる採番処理など。
- 生成するエンティティと同じパッケージに入れておいて、存在に気づかせる工夫をしておく
集約
- データの整合性を保つための方法
- ユニークキーの制約
- DBMSのユニークキー制約は活用したいが、インフラストラクチャでの実現になってしまう。
- 重複を許さないというドメインルールがコードから読み取れないので、データベースのユニークキーは、予防線/assertとしてつかいつつ、ドメインオブジェクトにも反映したい
- トランザクション制御
- AOP(アスペクト指向プログラミング)をつかった実現方法(Transactionalアノテーション)で、コードに自動でトランザクション範囲を埋め込む
- オブジェクトの変更や削除を、ユニットオブワークを経由しておこなうようにして、ユニットオブワークの中でトランザクションを制御する
- ユニークキーの制約
- 集約は、データを変更するための単位として扱われるオブジェクトの集まり
- 集約外から集約ルート以外の集約内のオブジェクトを操作できないようにする
- 「デメテルの法則」:直接の友達とだけ話すこと。オブジェクト同士のメソッド呼び出しに秩序をもたらすガイドライン
- 内部のフィールドは基本的にアクセスできないようにすべきだが、O/Rマッパーを使っているケースなどで困ることがある
- 「通知オブジェクト」を使うことで、専用のインタフェースを経由してマッピングさせる方法があるが、複雑になる。
仕様
- オブジェクトの評価をおこなうオブジェクトが仕様
- リポジトリなど他のオブジェクトを参照しなければならないドメインのルールを、値オブジェクトやエンティティにいれないことで、お互いの結合度を下げる
- 仕様の中でリポジトリを使用するのを避ける方法として、ファーストクラスコレクションがある
- 集合をクラスとして扱えるようにして、アプリケーションサービス側でリポジトリからファーストクラスコレクションへ入れ替えする
- 仕様では、ファーストクラスコレクションをつかって判定する
- リポジトリに仕様を引き渡して、仕様に合致するオブジェクトを検索させる手法もある
- 仕様とリポジトリが織りなすパフォーマンス問題を孕む可能性があるのは注意
- リードモデルとして、特殊な条件下のオブジェクトを検索したいときに、仕様やリポジトリといったパターンを使わない方法も選択する
- ページングは、SQLの
OFFSET
やFETCH NEXT @size ROWS ONLY
などを使うと良い - 読みこみだけであれば、CQS(Command-Query separation)とかCQRS(Command-Query Resposibility Segregation)で
- ページングは、SQLの
- 参考:リードモデルのN+1問題とCQRS
アーキテクチャ
- このあと読んだ別な本で解説がまとまっているので、ここでは割愛
感想
- この本から入って本当によかった気がします。ドメイン駆動開発の具体的実装のイメージをつけてから、抽象的なことをスタディすることができました。
- 今回C#に触れたのは初めてだったのですが、CやJavaをやったことがあれば、読むのには容易かったと思いますので、サンプルコードがC#のせいでこの本を読むのをためらう必要はないように思います。
- Pythonで書き直しながら進める部分も型ヒントが書けるようになったのでほとんど問題なかったですが、DjangoとPython向けのDIコンテナでの読み替えが難しかったです。
- 今回のスタディで、より「Python型ヒントちゃんと書きたい派」になりました。
- 6章でDTOを導入するハードルを下げるために、自動生成するツールをつくってしまえばよいという話。面白いと思いました。こういう独自のコード生成ツールとかlinterみたいなの、そろそろ自分もできるようにならないといけないと感じます。
今後の展望
- 次は増田本の感想記事書きます。
最終更新日: January 14, 2023