型を使ったワークフローの各ステップのモデリング
検証のステップ
code:text
substep "ValidateOrder" =
input: UnvalidatedOrder
output: ValidatedOrder OR ValidationError
dependencies: CheckProduceCodeExists, CheckAddressExists
入力に加えて、2 つの依存関係がある
1. 製品コードが存在するかどうかをチェックするための関数
2. 住所が存在するかどうかをチェックするための関数
https://scrapbox.io/files/669f4d5398f528001cf9073b.png
関数の型シグネチャは、後で実装する必要のある インタフェース になるので差し替えられる 1. 製品コードが存在するかどうかをチェックするための関数
type CheckProductCodeExists = ProductCode -> bool
2. 住所が存在するかどうかをチェックするための関数
code:fsharp
type CheckedAddress = CheckedAddress of UnvalidatedAddress
type AddressValidationError = AddressValidationError of string
type CheckAddressExists =
UnvalidatedAddress -> Result<CheckedAddress, AddressValidationError>
依存関係を定義できたので、検証のステップは以下のように型定義できる
入出力は型定義済みと仮定する
code:fsharp
type ValidateOrder =
CheckProductCodeExists // 依存関係
-> CheckAddressExists // 依存関係
-> UnvalidatedOrder // 入力
-> Result<ValidatedOrder, ValidationError> // 出力
部分適用 を容易にするため、依存関係を最初に置いている CheckAddressExists が Result を返すため、この関数の全体的な戻り値も Result にする必要がある
価格設定のステップ
code:text
substep "PriceOrder" =
input: ValidatedOrder
output: PricedOrder
dependencies: GetProductPrice
このステップには、製品コードが与えられると価格を返す 1 つの依存関係がある
https://scrapbox.io/files/669f506a9680bc001da44da4.png
依存関係を文書化する
code:fsharp
type GetProductPrice =
ProductCode -> Price
ステップ全体の型定義
code:fsharp
type PriceOrder =
GetProductPrice // 依存関係
-> ValidatedOrder // 入力
-> PricedOrder // 出力
注文確認のステップ
このステップでは、確認書を作成して、顧客に送信する
「確認書」のモデル作成
電子メールで送信する予定の HTML 文字列を含む
code:fsharp
type HtmlString = HtmlString of string
code:fsharp
type OrderAcknowledgment = {
EmailAddress: EmailAddress
Letter: HtmlString
}
Letter の作成をどうする?
顧客情報と注文内容に基づいて、テンプレートのようなものから作成される
code:fsharp
type CreateOrderAcknowledgmentLetter = PricedOrder -> HtmlString
確認書を「送る」処理をどう表現するか?
Letter 同様、依存関係として考える
具体的な実装は考えずに、必要なインタフェースに集中する
一般的に駄目なインタフェースの例
type SendOrderAcknowledgment = OrderAcknowledgment -> unit
🙅: 呼び出し元から送られたかどうかを判別できない
type SendOrderAcknowledgment = OrderAcknowledgment -> bool
🙅: bool は情報量が非常に少ないので避けるべき
type SendOrderAcknowledgment = OrderAcknowledgment -> OrderAcknowledgmentSent option
改善例
送った(Sent)・送っていない(NotSent) の 選択型 を利用する code:fsharp
type SendResult = Sent | NotSent
type SendOrderAcknowledgment =
OrderAcknowledgment -> SendResult
出力
「送信した」イベントを返すようにする
code:fsharp
type OrderAcknowledgmentSent = {
OrderId: OrderId
EmailAddress: EmailAddress
}
ステップ全体の型定義
code:fsharp
type AcknowledgeOrder =
CreateOrderAcknowledgmentLetter // 依存関係
-> SendOrderAcknowledgment // 依存関係
-> PricedOrder // 入力
-> OrderAcknowledgmentSent option // 出力
確認書が送信されていない可能性があるため、出力の型は option としている
イベントの作成
code:text
workflow: "Place order" =
input: UnvalidatedOrder
output (on successs):
OrderAcknowledgmentSent
AND OrderPlaced (to send to shipping)
AND BillableOrderPlaced (to send to billing)
output (on error):
ValidationError
OrderAcknowledgmentSent イベント以外にも、 OrderPlacedと BillableOrderPlaced イベントを返す必要がある
OrderPlaced(注文後確定したイベント): 発送用
PricedOrder のエイリアス
code:fsharp
type OrderPlaced = PricedOrder
BillableOrderPlaced(請求可能な注文が確定したイベント): 請求用
PricedOrder の部分集合
code:fsharp
type BillableOrderPlaced = {
OrderId: OrderId
BillingAddress: Address
AmountToBill: BillingAmount
}
返すイベントをどう表現するか?
code:fsharp
type PlaceOrderResult = {
OrderPlaced: OrderPlaced
BillableOrderPlaced: BillableOrderPlaced
OrderAcknowledgmentSent: OrderAcknowledgmentSent option
}
更新が難しくなる
e.g. ワークフローに新しいイベントを追加する
ワークフローがイベントのリストを返すようにする
各要素は OrderPlaced、BillableOrderPlaced、OrderAcknowledgmentSent のいずれか
code:fsharp
type PlaceOrderEvent =
| OrderPlaced of OrderPlaced
| BillableOrderPlaced of BillableOrderPlaced
| OrderAcknowledgmentSent of OrderAcknowledgmentSent
ワークフロー全体を壊すことなく更新できる
より一般的な概念を作ることもできる
e.g. OrderTakingDomainEvent(受注ドメインイベント): 受注ドメイン内のすべてのイベントの選択型
ステップ全体の型定義
code:fsharp
type CreateEvents =
PricedOrder -> PlaceOrderEvent list