型を使ったワークフローの各ステップのモデリング
from Modeling Workflows as Pipelines
ステートマシン を用いて、注文確定ワークフロー(Modeling Workflows as Pipelines#669cfd2c75d04f00000e97d2)の各ステップをモデリングする
検証のステップ
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 文字列を含む
HTML 文字列も 単純型 としてモデリング
code:fsharp
type HtmlString = HtmlString of string
code:fsharp
type OrderAcknowledgment = {
EmailAddress: EmailAddress
Letter: HtmlString
}
Letter の作成をどうする?
顧客情報と注文内容に基づいて、テンプレートのようなものから作成される
依存関係 として考える
型を使ったワークフローの各ステップのモデリング#669f4eb375d04f0000e9f9dd
型を使ったワークフローの各ステップのモデリング#669f500075d04f0000e9f9e7
code:fsharp
type CreateOrderAcknowledgmentLetter = PricedOrder -> HtmlString
確認書を「送る」処理をどう表現するか?
Letter 同様、依存関係として考える
具体的な実装は考えずに、必要なインタフェースに集中する
一般的に駄目なインタフェースの例
type SendOrderAcknowledgment = OrderAcknowledgment -> unit
🙅: 呼び出し元から送られたかどうかを判別できない
type SendOrderAcknowledgment = OrderAcknowledgment -> bool
🙅: bool は情報量が非常に少ないので避けるべき
type SendOrderAcknowledgment = OrderAcknowledgment -> OrderAcknowledgmentSent option
OrderAcknowledgmentSent は ドメインイベント
型を使ったワークフローの各ステップのモデリング#66a0854075d04f000015258d
🙅: ドメインイベント の型を介して、ドメイン と サービス 間に結合が生じる
改善例
送った(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
}
更新が難しくなる
オープン・クローズドの原則 に違反 radish-miyazaki.icon
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