Domain Modeling Made Functionalを読んでみる
この記事について
私が気になった部分やわからなかった部分をメモしていきます。
Domain Modeling Made Functionalの本を要約解説するものではありません。
1章 Introducing Domain-Doriven Design
この章は、DDDの基本的な思想について説明されている。
例えば、ドメインエキスパートだけでなく開発チーム全体がドメインについて理解する必要があること、その理解をチームで共有することなど。
高次元のドメインの概念だと、紙だろうがシステムだろうが変わらないはずであるというのは良い言葉。
結構ドメインイベント周りが厚く説明されていて、これからキーになりそう感ある。
ドメインイベントとそれを実行するコマンドのつながりを理解する必要があるっぽい。
1.コマンドが実行される
2.コマンドが成功する
3.ドメインイベントが起きる。
4.ドメインイベントが次に起こすイベントのコマンドを叩く。
この流れで、ひとつながりのプロセスが実行されていく。
大きなドメインは理解できる程度に小さく切り分ける。
"ドメインエキスパートがやっていること"がドメインであるっていうのは、なかなか思い切っているけど良い定義だと思った。
ドメインエキスパートが言うこと・考えていることの一つひとつがドメインそのものと捉えても良さそう。
業務を理解できたら、境界づけられたコンテキストを正しく切ることがまずは大事。
境界を見つけるコツは、
- ドメインエキスパートに聞くこと
- チームや部署の区分けに注目すること
- 大きなコンテキストを作らないこと
- 独立したコンテキストになっているかに着目すること
どんな設計も静的なものではない。
コアなドメインを特定する。基本的には、顧客が最も価値を感じる領域がコアになる。
ユビキタス言語を作りましょう。
トリガー(コマンド)とイベントの違いがちょっと分かりにくかった。
一章はここまで。
2章
インタビューは、先入観を持たずドメインの知識を得ることに専念する。
ワークフローの出力は常に次のイベントをトリガーするものである。
→関数型っぽい話しているけど、いまいちわからない。
注文確認書が出力ではないと言ったこととの違いは何か?アクションではないといけない理由は何?
トリガーと前章のコマンドの違いは何?
データベースドリブンではなく、モデルドリブンに設計しよう。
→結構難しいですね。。どうしてもモデルを解釈しながらDBを考えてしまうところがある。みなさんどうですか?
データベースから設計してしまうとモデルをデータベースの都合で捻じ曲げてしまう⇨なるほど。やってしまいそう。
クラスドリブンもだめ。辛い。
技術的なアイデアは後回しにして、まずはドメインに耳を傾けるのだ!
テキストベースの擬似プログラミングでドメインを文書化していく。
→あくまでクラスやDBを作るのではなく、ドメインを少し構造化して捉えることに集中する。
カタログのコピーを持っている件も、ドメインにはならないかもしれないけれど、ニーズ(=各部署独立して働きたい)を理解する上では必要
→こういう無駄そうなことしている時ってサラッと話を流してしまうことあるけど、これらからニーズを拾っていくことも重要なのね。
ユビキタス言語は結構細かく厳密に作成していく。甘くしてしまうと、設計が形を為さなくなる。
ドメインエキスパートのメンタルモデルにも着目しながら、ドメインを設計する。
3章
インタビューやイベントストーミングによって知識を補ってきたが、それをどう設計に繋げるのか。
プロトタイプを作って早めにフィードバックをもらう
C4の話は抽象的すぎてちょっとわからない。
初めから境界を正しく設計するのは難しい(そもそも境界は変化するもの)
モノリスに作ってから、必要に応じてリファクタリングしていくのが良い方法。
コンテキスト間のメッセージングはいろんな方法がある。キュー(Pub/Sub?)は良い方法の一つ。非同期でマイクロサービスでもモノリスでもどちらでも使える。
関数を呼ぶのって依存関係なし(独立している)といえるの?
コンテキスト間で共有可能なDTOを使うことで独立にすることが可能→なるほどね。。DTOをコンテキスト外に定義することにちょっと抵抗感ある。
共通のDTOでメッセージングするのではなく上流DTO⇨JSON/XML⇨下流DTOと言う感じにする。これは良さそう。だけど、一旦JSON挟むの面倒だな。。
[Anything inside the bounded context will be trusted and valid, while anything outside the bounded context will be untrusted and might be invalid.]外部に依存していないことが言いたいのかな?
インプットゲート部分でバリデーションかけるのは良さそう。
アウトプットはセキュリティ意識!
「共有カーネル」・・・お互いが共通のものに依存している。その共通のものの変更には協議がいる
「コンシューマー・サプライヤー」・・・下流が上流に必要なものの提供を求める形式(例:オーダーと通知)
「順応者」・・・下流は上流の変更をそのまま受け入れる形式(例:プロダクトカタログ)
この三つの形のリレーションがある。なるほど。
腐敗防止層パターン・・・レガシーなものとの間に層を設けて独立させる⇦これもインプットゲートになるのか
「The relationship between the order-taking and shipping contexts will be a “Shared Kernel,” meaning that they will jointly own the communications contract.」ここちょっと分かりにくい。。
逆コンウェイの法則!
4章
Understanding Functions
関数のイメージ:何かが入り、何らかの形で変換され出てくる。
Types and Functions
関数型プログラミングでは、変数やオブジェクトではなく値と言う単語を使う。
オブジェクト指向では、オブジェクトが値と動作をカプセル化したもので、内部に状態を保つことなる。
これに対して、関数型ではほぼ全てが値であり、関数も「ある型から別の型に値を変更する」型の値であると言える。
図として関数を書いてみる
値、オブジェクト、変数の違い
値:型のメンバーのこと。不変であり、関数も入力(型)→出力(型)に変更する形のメンバーの一つと考えられる
オブジェクト:状態をもつ(状態は変化するもの)
変数:変数は”変化”するもの
Composition of Types
ANDタイプとORタイプの二つがある
ANDタイプ:レコードと呼ぶ。複数の型の複合。F#の複合型のは、クラスとかGoだと構造体にあたる物。
ORタイプ:チョイスと呼ぶ。
101:チョイスは、enumみたい。チョイスにはタグをつける必要がある。
101:これはチョイスに同じ型を複数登録することがしばしばあるため、それらを見分けるときに使う。→結構イメージついてない
関係ないけど、リンゴの型に富士が出てきたのは結構熱い。
104:代数型プログラミングというのはなかなか面白いのかも?「代数的な型システムとは、単純に、すべての複合型が、より小さな型をANDまたはORすることによって構成されるシステムのことである。」
algebraic type systemの話は面白そうなので、少し調べる
Working with F# Types
105:パターンマッチを使って、関数の出力を変化させる方法(match with)の紹介
105:[let printQuantity aOrderQty = ...]の書き方をするとなると、新しい型を追加するたびにこんなふうに全部のメソッドを変更しないといけないのだろうか。ちょっと辛いな。新しい型を追加した時の影響範囲の調べ方どうやってやるんだろうか。
Building a Domain Model by Composing Types
1.単純型(値オブジェクト)を作る。
2.次にレコード型(構造体)、チョイス型を作る。
支払い方法の定義だとまず値オブジェクトとして、それぞれの支払い方法を定義した後、それらをチョイス型”支払い方法”として定義している。
支払いのレコードは、金額、通貨単位、支払い方法のレコードとして定義
↑のメソッドは支払い方を定義したもの。現金かカードかなど。
そしてこれらの情報をまとめて、「支払い」という一つの単位ができる。
108:ここまでをまとめると、↑まずは型を作り、その型の遷移をメソッドとして定義していく。というのが関数型の流れっぽい。
Modeling Optional Values, Errors, and Collections
レコードとチョイスはnullを許容していない。
Option<T>をつけるとNullが許容された型を定義できる
オブションラベルもある
エラーの書き方
Result型を定義Result<'Success,'Failure>
これは、チョイス型で、関数でSuccess,Failureのどちらかを返すように作れる
例:type PayInvoice = UnpaidInvoice -> Payment -> Result<PaidInvoice,PaymentError>
PaymentErrorはチョイス型で宣言しておくと、エラーの種別も作成可能
戻り値のない関数
何も返却しないメソッドは書けないが、そういうことをしたい時はunitを使う。
例えば顧客を保存するメソッドの戻り値をunitにするなど。
引数が何もないときも引数にunitを置く。
リスト、コレクション
list, array, ResizeArray, seqが存在する
listはイミュータブルで不変長。
ドメインモデリングではlistと使う
optionと同じように宣言できる
type Order = { OrderId : OrderId Lines : OrderLine list // a collection }
Organizing Types in Files and Projects
コンパイル順の早いファイルは、遅いファイルを参照できない。
type→domain→common→Order Takeing→Shippingの準にコンパイルする
同ファイル内でも同様で、型の宣言は上から順に宣言されている必要があり、下流の型を使って上流の型をコンパイルできない。
例外的に可能にする方法(rec, and)も用意されているが、リリース前までには正しい依存関係に並べ替える方が良い。
F# の 文法まとめ
- 関数関連
let で関数定義を宣言
returnはなく、最後に表現されたものが戻り値になる。
関数の範囲はインデントで区切る(Pythonみたい)
関数で型を宣言する必要はなく、推論してくれる。
汎用型は'aと表現する
同じかどうか判定する比較演算子は=一つ
レコード型の値の宣言
let aPerson = {First="Alex"; Last="Adams"}
レコード型の値の分解
let {First=first; Last=last} = aPerson
Discussion