はじめに
忘れっぽいので自分用のメモです
自分よ、これを見て思い出しておくれ
軽量DDD実績ができたら、ちゃんと清書します。
値オブジェクト
例えば、const fullname = "tanaka taro"
というstringがあったら、どれが姓と名がどちらかわかりませんね。
少なくとも前が姓で後ろが名とかそういうルールがあったとしてもコードからは何も意図は伝わりません。
また、比較ロジックの散布もされるでしょう。
仕様が変わったら全文検索して探して修正する必要がありそうですね。
ですので、FullNameオブジェクトにしちゃおうって感じです。
class FullName {
constructor(readonly firstName: string, readonly familyName: string) {}
get getFullName() {
return `${this.firstName} ${this.familyName}`;
}
}
まぁだいたいこんな感じです。
また、ValueObjectはimmutableです。
ですので、姓又は名が変わった場合には再生成してあげる必要があります。
エンティティ
Entityは同一性を持ちます。
つまりId, 識別子を持っています。
また、Entityはmutableです。
どういうことかというと、
Tanaka Taroさんがいます。
世の中にはTanaka Taroさんがもう1人いました。
これは、UserEntityのFullNameフィールドの値が同一だったって事ですね。
しかし、1人目のTanakaTaroと2人目のTanakaTaro2はきっと別人です。
ですので、UserIdというValueObjectが存在し、それで同一性を基本的には判定します。
1人目のTanakaTaroさんが結婚して姓が変わってSuzuki Taroになることもあるでしょう。
その場合はUserEntityがChangeNameメソッドを持っていそうです。
これがmutableのわけです。
また、Entityのフィールドは基本的にはprivateにしましょう。
フィールドを変更する必要がある場合は、Entityのメソッドを使いましょう。
しかしフィールドをprivateにしてしまうと、repositoryがEntityのフィールドを取得できなくなるので、なんだかんだgetterが用意されたり、又は通知パターンを利用して解決する方法があるそう。
ドメインサービス
例えば、fullname重複が禁止されているアプリで、UserEntityがIsDuplicatedメソッドを持ってみます。
Userが自分自身に、俺の名前って重複してる?って問い合わせるのは不自然
OK, そういうロジックはドメインサービスに書こう!
しかし、重複してるかどうかを知るには永続化装置に問い合わせる必要がある。
永続化装置周りのコードは次に登場するリポジトリに書きまっす。
なのでそういうドメインサービスは基本リポジトリをDIされます。
リポジトリ
リポジトリは永続化装置とのやり取りをします。
基本的に永続化装置からEntityの再構築と、Entityの永続化、削除の3つの仕事をする感じ(なんか漏れてそう)。
基本saveとfind系。昨今だとリアルタイムデータを扱うためにsubscribeもありそう。
一応テスト用にインメモリリポジトリを用意したりすると、テストが超便利みたいね。
create entity周りの話
ちなみにEntityに必要なIdがない状態で、構築するのに最低限必要なデータだけを受け取って、Entityを保存して採番したりする処理もここに書かれたりする。
しかし、そもそもIdのないEntityをEntityとは呼べないので(同一性がないタイミングが発生するから)、Entityを構築するのに必要なデータを引数で受け取るイメージ。
しかし、EntityをcreateするのはFactoryのが良さそう。
Factory
まぁ、工場って感じです。
Entityの採番処理もDAO使ってこのなかでやって良さげ。
ちなみにfirestoreはドキュメントで使える採番用IDをfirestoreにアクセスしなくても華麗に取得する方法があるんだなー(๑´ڡ`๑)素晴らしい
ちなみにDAO又はUUID経由で採番したからってEntityがDBに保存(永続化)されてるとは限らないぜ。
なぜなら永続化はリポジトリの仕事だからな(๑´ڡ`๑)
また、これにid採番とかそういうの全部任せたらrepositoryやentityがidが存在しないentityとかの妥協、例外ケースを考えなくて良いんで、普通にコード書くのがより早く、簡単になりそう。
つまりイケてる。
アプリケーションサービス
これはmvcで言う所のcontrollerと1対1の関係になることが多い。(理由はcontrollerの責務にある。controller項に書く
アプリケーションサービスはドメインモデルを操作して振る舞いを実現する。
ドメインモデルとはなんなのかって話は次の項目を参照。
clean archiectureで言う所のusecaseと同じ。
例えば、createUser、updateUser。findMyBooksとか。これを見るだけで素晴らしいアーキテクチャで作られているなら、これが何のアプリなのかわかるらしい。クリーンアーキテクチャの本でも、「叫ぶアーキテクチャ」ってあったしね。
いつか実現してみたい。きっと達成感が凄そう。
少なくともなんとなく業務で自分でパッと実現できるレベルのものではないのは確か。修行がいるぜ(๑´ڡ`๑)
ドメインモデル
ドメインモデルとは、ビジネスロジックをカプセル化した「物」を指す。
Entity, DomainService, ValueObject 等が該当するであろう。
補足
FactoryとRepositoryはビジネスロジックを含まないのでドメインモデルではない。
DIによる依存管理で作った場合はただのインターフェイスになる。
クリーンアーキテクチャで言う所の注入される実態はusecase外にあり、プラグインになって円の外側にいるので。
クリーンアーキテクチャで言う所のEntity(軽量DDDで言う所のEntityとは全くの別物。エンタープライズルールと定義されている)がドメインモデルに該当するので間違いなさそう。
MVC
歴史的経緯とかあまり詳しくないが (いつも忘れてしまうのでうんちく言えん(´;ω;`))
とりあえずロジックはModelに書く。
GUIはView
GUIからの入力データ、api形式の通信等から来るリクエストをModelが解釈できるデータ形式に当てはめたり、正しくないデータなら突っぱねたりするのがController。
なのでこのModel部分が完全にアプリケーションサービスと一致する。
また、アプリケーションサービスの出力結果は必ずしもViewが要求するデータ形式とは限らない。
勿論偶然の一致はもちろんあるが、それはDRYとかが適用されるケースではない。
なので、アプリケーションサービスの出力結果をpresenter関数とかに通してViewが欲しいデータ形式に変換するレイヤーが必要。
依存関係等の図
今度作ります。。。
これあるととても飲み込み早そう。
考察
技術的な理由からDIコンテナを用意して、上手く依存管理することができない環境って割とあると思う。例えばコンストラクタDIが封じられている環境とか。
その場合はサービスロケータとかを使うと良さそう。LaravelのFacadeがそれに該当するんだとか。
構成としては、DIコンテナにインスタンスを突っ込んどいて、サービスロケータがそのDIコンテナに登録されているインスタンスに処理を委譲する感じ。そうすれば、テスト時はこのインスタンスを登録して、本番時にはこのインスタンスを登録してってのができる。
ちなみにFacadeパターンとLaravelのFacadeは別ものらしい。
参考資料
bottomup-ddd: https://nrslib.com/bottomup-ddd
スライド系: https://nrslib.com/event-after-bottomup-ddd-1