TL;DR
内容
ワークフローは関数の型で表す
単純な値のモデリングと複雑なデータのモデリングで、ユビキタス言語の名詞はモデリングできるようになった。一方、動詞にあたるビジネスプロセスや、それを構成するワークフローは、関数の型としてモデリングする。たとえば、注文書を検証するワークフローは次のように書ける。
type ValidateOrder = UnvalidatedOrder -> ValidatedOrder複数の入力と出力
関数には入力と出力が 1 つずつしかないが、ワークフローには複数の入力や出力(ドメインイベント)を持つものがある。
出力をすべて返す場合は、すべてを格納できるレコード型を使う。
type PlaceOrderEvents = {
AcknowledgmentSent : AcknowledgmentSent
OrderPlaced : OrderPlaced
BillableOrderPlaced : BillableOrderPlaced
}出力のどれか 1 つを返す場合は、選択型(判別共用体)を使う。
type EnvelopeContents = EnvelopeContents of string
type CategorizedMail =
| Quote of QuoteForm
| Order of OrderForm
// etc同じワークフローに入力の選択肢がある場合も、選択型を使う。複数の入力がすべて必須の場合は、各入力を別々のパラメータとして渡すか、レコード型にまとめる。
type CalculatePrices = OrderForm -> ProductCatalog -> PricedOrder
type CalculatePricesInput = {
OrderForm : OrderForm
ProductCatalog : ProductCatalog
}
type CalculatePrices = CalculatePricesInput -> PricedOrderレコード型にするのは、すべての入力が常に必須で、互いに強く結びついている場合だけにする。別々のパラメータとして渡せば、依存性の注入ができる。
関数のシグネチャでエフェクトを文書化する
検証のように失敗する可能性がある処理は、第4章で見た Result 型を使って表す。
type ValidateOrder =
UnvalidatedOrder -> Result<ValidatedOrder, ValidationError list>
type ValidationError = {
FieldName : string
ErrorDescription : string
}関数型プログラミングでは、関数が主な出力以外に行うことをエフェクトと呼ぶ。Result はエラーのエフェクトを持つ可能性があることを、Async は非同期であることを文書化する。複数のエフェクトを組み合わせることもできる。
type ValidateOrder =
UnvalidatedOrder -> Async<Result<ValidatedOrder, ValidationError list>>ただし、すべてのエフェクトをそのまま書くと読みにくい。型エイリアスを使ってまとめるとよい。
type ValidationResponse<'a> = Async<Result<'a, ValidationError list>>
type ValidateOrder =
UnvalidatedOrder -> ValidationResponse<ValidatedOrder>