個々の文脈で完全なデータ型を定義する
LifecycleのあるEntityを個々の状態で定義する
Entityのmodelingをする際に、それが本当に過不足のない定義ができているか?を確認する必要がある
不足があれば足されるだけなので、問題は寧ろ過剰な定義になっていることで生じる
無闇に大きなEntityはその時点で設計ミスしている
何が嬉しいのか
Entityが小さくなる
新しい状態が追加されたときに既存の状態に影響を及ぼさずに追加できる
設計時にどういう思考をするか?
productの中で、このEntityは
どういう状態になるか?
どういう文脈で使われるか?
を考える
例えば
「投稿」にしても、編集中、下書き、作成済みなどに遷移しうる
「ユーザー」にしても、ゲスト、一般会員、企業アカウントなどの種類がありうる
これらは種類ごとに別々に型を定義すべき、という主張mrsekut.icon
パターン数が多すぎてしまう場合はどうするか?
Orderの例 ref 『Domain Modeling Made Functional』.icon p.122~
Unvalidated Order
→ Validated Order
→ Priced Order
それぞれの状態におけるOrderを別々のデータ型として定義する
code:hs
data ValidatedOrder = ValidatedOrder
{ orderId :: OrderId
, customerInfo :: CustomerInfo
...
}
data PricedOrder = PricedOrder
{ orderId :: OrderId
, customerInfo :: CustomerInfo
...
, amountToBill :: BillingAmount
...
}
個々の状態で完全なデータ型を定義する
上の例では、amountToBillはPricedOrderの状態でのみ必要になるfieldである
そのため、それ以前の状態であるValidatedOrderなどではそれを持たす必要はない
というか持たすべきではない
個々の状態を作った上でOrderという直和型を定義する
code:hs
data Order = Invalid InvalidOrder
| Unvalidated UnvalidatedOrder
| Priced PricedOrder
Userはゲストだったり認証済みの会員だったりする
これらも別々のEntityとして定義する
code:purs(hs)
type ProfileRep row =
( username :: Username
, bio :: Maybe String
, image :: Maybe Avatar
| row
)
type Profile = { | ProfileRep () }
type ProfileWithEmail = { | ProfileRep (email :: Email) }
type ProfileWithEmailPassword = { | ProfileRep (email :: Email, password :: Maybe String) }
type Author = { | ProfileRep (relation :: Relation) }
Userの取りうる個々の状態ごとにデータ型を定義している
pursではRow型を使ってRecordのGenricsぽいことができるのでそれを利用している
ProfileRepという共通の型を定義し、個々でこれを拡張した型を定義する
ProfileRepもそのまま使うのではなく、Profile型としてwrapしている
tsでも同じような定義の仕方ができるmrsekut.icon
この核となっているProfileRepの「適切さ」の判断基準はどこにあるのか?
これはもっと小さくはできないのか?これは本当にここまで小さくして良いのか?
これは再利用性を見込んだ切り出し方なのだろうか?
といった疑問に答える必要があるmrsekut.icon
「個々の状態で完全なデータ型を定義する」はこの時点でクリアしている
疑問に思っているのは「更に小さくできないか?またそうするべきか?」
一つの目安としては、このrealworldはSNSっぽいサービスなので、ユーザー情報を表すProfileには2種類あることがドメインの観点からわかる
自分と、他のユーザー
だからそれらの共通項を一つの型として切り出す、というのはわかる
Entityにfetched :: Boolのようなflugを持たすべきではない
このような型を組み込む
code:purs(hs)
data RemoteData err res
= NotAsked
| Loading
| Failure err
| Success res
view依存の例
code:ts
type User = PrimitiveUser | UserWithPosts;
type PrimitiveUser = UserRep & {
kind: "primitive";
};
type UserWithPosts = UserRep & {
kind: "withPosts";
address: string;
posts: Post[];
};
type UserRep = {
name: string;
age: number;
};
関連
Documentationする時点でこの観点を意識して作っておく
OOPでの似たようなアプローチ
参考
モノに着目するのではなく、取り扱われるコンテキストに着目してmodelingする
役割駆動設計はOOPに寄っている感じがする