非同期エフェクトの追加
元々 Result エフェクトだけではなく、Async エフェクトも用いていた
code:fsharp
type AsyncResult<'success, 'failure> = Async<Result<'success, 'failure>>
module AsyncResult =
let map f (x:AsyncResult<_,_>) : AsyncResult<_,_> =
Async.map (Result.map f) x
let mapError f (x:AsyncResult<_,_>) : AsyncResult<_,_> =
Async.map (Result.mapError f) x
let retn x : AsyncResult<_,_> =
x |> Result.Ok |> Async.retn
let bind
(f: 'a -> AsyncResult<'b,'c>) (xAsyncResult : AsyncResult<_, _>) :AsyncResult<_,_> =
async {
let! xResult = xAsyncResult
match xResult with
| Ok x -> return! f x
| Error err -> return (Error err)
}
let ofResult x : AsyncResult<_,_> =
x |> Async.retn
type AsyncResultBuilder() =
member this.Return(x) = AsyncResult.retn x
member this.Bind(x, f) = AsyncResult.bind f x
let asyncResult = AsyncResultBuilder()
ofResult は Result 型の値を AsyncResult に 型を持ち上げる 関数 asyncResult は、result と同じように用いる
検証のステップは、以下のように置き換えられる
code:fsharp
let validateOrder: ValidateOrder =
fun checkProductCodeExists checkAddressExists unvalidatedOrder ->
asyncResult {
let! orderId =
unvalidatedOrder.OrderId
|> OrderId.create
|> Result.mapError ValidationError
|> AsyncResult.ofResult
let! customerInfo =
unvalidatedOrder.CustomerInfo
|> toCustomerInfo
|> AsyncResult.ofResult
let! checkedShippingAddress =
unvalidatedOrder.ShippingAddress
|> toCheckedAddress checkAddressExists
let! shippingAddress =
checkedShippingAddress
|> toAddress
|> AsyncResult.ofResult
let! billingAddress = ...
let! lines =
unvalidatedOrder.Lines
|> List.map (toValidatedOrderLine checkAddressExists)
|> Result.sequence
|> AsyncResult.ofResult
// すべてのフィールドの準備ができたら、それを元にレコード型を作成して返す
let validatedOrder: ValidatedOrder = {
OrderId = orderId
CustomerInfo = customerInfo
ShippingAddress = shippingAddress
BillingAddress = billingAddress
OrderLines = lines
}
return validatedOrder
}
主な変更点
住所の検証を 2 つの部分に分けた
理由
エフェクトをすべて元に戻すと、CheckAddressExists は AsyncResult を返す
code:fsharp
type CheckAddressExists =
UnvalidatedAddress -> AsyncResult<CheckedAddress, AddressValidationError>
AddressValidationError
code:fsharp
and AddressValidationError =
| InvalidFormat of string
| AddressNotFound of string
このシグネチャを適合させたい
エラーの型をサービス固有の AddressValidationError ではなく、ドメインの ValidationError にマッピングする必要がある
toCheckedAddress
code:fsharp
let toCheckedAddress (checkAddress: CheckAddressExists) address =
address
|> checkAddress
|> AsyncResult.mapError (fun addrError ->
match addrError with
| AddressNotFound -> ValidationError "Address not found"
| InvalidFormat -> ValidationError "Address has bad format"
code:fsharp
let placeOrder: PlaceOrder =
fun unvalidatedOrder ->
asyncResult {
let! validatedOrder =
validateOrder checkProductCodeExists checkAddressExists unvalidatedOrder
|> AsyncResult.mapError PlaceOrderError.Validation
let! pricedOrder =
priceOrder getProductPrice validatedOrder
|> AsyncResult.ofResult
|> AsyncResult.mapError PlaceOrderError.Pricing
let acknowledgmentOption = ...
let events = ...
return events
}