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>

キーワード