타입을 사용하여 워크플로의 각 단계 모델링
workflow "Place Order" =
input: OrderForm
output:
OrderPlaced event (put on a pile to send to other teams)
OR InvalidOrder (put on appropriate pile)
// step 1 - 순수하지 않은 스텝
do ValidateOrder
If order is invalid then:
add InvalidOrder to pile
stop
// step 2
do PriceOrder
// step 3
do SendAcknowledgmentToCustomer
// step 4
return OrderPlaced event (if no errors)
(주 : 종속성의 입출력에 오직 primitive type과 도메인 타입만 존재하는 것을 봅시다. 사이드이펙트에도 주목하세요)
(ex - 검증로직 함수, 도메인 타입 객체를 입력으로 받아. 메일 템플릿(HtmlString of string)을 리턴하는 함수)
The Validation Step
substep "ValidateOrder" =
input: UnvalidatedOrder
output: ValidatedOrder OR ValidationError
dependencies: CheckProductCodeExists, CheckAddressExists
type CheckProductCodeExists =
ProductCode -> bool
// ^input ^output
(주 : 프로덕트코드 검증 로직은 우리 도메인의 코드였다면 이번엔 외부 시스템을 이용해 검증합니다.)
type CheckedAddress = CheckedAddress of UnvalidatedAddress
type AddressValidationError = AddressValidationError of string
type CheckAddressExists =
UnvalidatedAddress -> Result<CheckedAddress,AddressValidationError>
// ^input ^output
type ValidateOrder =
// 프로덕트 코드 검증 종속성 함수
CheckProductCodeExists // dependency
// 주소 검증 종속성 함수
-> CheckAddressExists // dependency
// 실제 데이터 입력
-> UnvalidatedOrder // input
// 실패할 수 있는 타입 Result로 표현!
-> Result<ValidatedOrder,ValidationError> // output
매개변수 순서에서 종속성을 먼저 배치하고 출력 유형 바로 앞에 입력 유형을 두 번째로 배치했습니다.
그 이유는 부분 적용을 더 쉽게 하기 위함입니다(종속성 주입과 기능적으로 동등함).
구현 장에서 이것이 실제로 어떻게 작동하는지 살펴봅니다.
가격 측정 단계
substep "PriceOrder" =
input: ValidatedOrder
output: PricedOrder
dependencies: GetProductPrice
type GetProductPrice =
ProductCode -> Price
type PriceOrder =
GetProductPrice // dependency
-> ValidatedOrder // input
-> PricedOrder // output
주문 승인 단계
substep "SendAcknowledgmentToCustomer" =
input: PricedOrder
output: None
create acknowledgment letter and send it
and the priced order to the customer
type HtmlString =
HtmlString of string
type OrderAcknowledgment = {
EmailAddress : EmailAddress
Letter : HtmlString
}
type CreateOrderAcknowledgmentLetter =
PricedOrder -> HtmlString
(HtmlString은 Simple Type으로 여러 도메인에서 같이 써도 무방한 타입입니다.)
type SendOrderAcknowledgment =
OrderAcknowledgment -> unit
type SendOrderAcknowledgment =
OrderAcknowledgment -> bool
부울은 일반적으로 디자인에서 좋지 않은 선택입니다.
왜냐하면 그것들은 매우 유익하지 않기 때문입니다.
bool 대신 간단한 Sent/NotSent 선택 유형을 사용하는 것이 좋습니다.
type SendResult = Sent | NotSent
type SendOrderAcknowledgment =
OrderAcknowledgment -> SendResult
type SendOrderAcknowledgment =
OrderAcknowledgment -> OrderAcknowledgmentSent option
type OrderAcknowledgmentSent = {
OrderId : OrderId
EmailAddress : EmailAddress
}
type AcknowledgeOrder =
CreateOrderAcknowledgmentLetter // dependency
-> SendOrderAcknowledgment // dependency
-> PricedOrder // input
-> OrderAcknowledgmentSent option // output
반환할 이벤트 만들기
지금까지 각 단계에서 리턴한 아웃풋들을 모아봅시다.
각 아웃풋은 다음 단계의 인풋으로 작동하였습니다.
주문 검증 : Result<ValidatedOrder,ValidationError>
가격 책정 : PricedOrder
주문 승인 : Option<OrderAcknowledgmentSent>
//>States2b
type PricedOrder = {
OrderId : DotDotDot
CustomerInfo : CustomerInfo
ShippingAddress : Address
BillingAddress : Address
// different from ValidatedOrder
OrderLines : PricedOrderLine list
AmountToBill : BillingAmount
}
//<
type OrderPlaced = PricedOrder
type BillableOrderPlaced = {
OrderId : OrderId
BillingAddress: Address
AmountToBill : BillingAmount
}
type PlaceOrderResult = {
OrderPlaced : OrderPlaced
BillableOrderPlaced : BillableOrderPlaced
OrderAcknowledgmentSent : OrderAcknowledgmentSent option
}
그러나 시간이 지남에 따라 이 워크플로에 새 이벤트를 추가할 가능성이 매우 높으며
type PlaceOrderEvent =
| OrderPlaced of OrderPlaced
| BillableOrderPlaced of BillableOrderPlaced
| AcknowledgmentSent of OrderAcknowledgmentSent
type CreateEvents =
PricedOrder -> PlaceOrderEvent list
사이드 이펙트를 문서화하기
사이드 이펙트의 타입 시그니처는 상위 함수 타입 시그니트에 이펙트를 줍니다.
검증 단계에서의 이펙트
type CheckProductCodeExists = ProductCode -> bool
type AsyncResult<'success,'failure> = Async<Result<'success,'failure>>
type CheckAddressExists =
UnvalidatedAddress -> AsyncResult<CheckedAddress,AddressValidationError>
type ValidateOrder =
CheckProductCodeExists // dependency
-> CheckAddressExists // AsyncResult dependency
-> UnvalidatedOrder // input
-> AsyncResult<ValidatedOrder,ValidationError list> // output
가격 책정 단계의 사이드 이펙트
type PricingError = PricingError of string
type PriceOrder =
GetProductPrice // dependency
-> ValidatedOrder // input
-> Result<PricedOrder,PricingError> // output
승인 단계의 이펙트
AcknowledgeOrder 단계에는 CreateOrderAcknowledgementLetter 및 SendOrderAcknowledgement라는 두 가지 종속성이 있습니다.
CreateOrderAcknowledgementLetter 함수가 오류를 반환할 수 있습니까? 아마 아닐 것입니다.
로컬이고 캐시된 템플릿을 사용한다고 가정합니다.
따라서 전체적으로 CreateOrderAcknowledgementLetter 함수에는 타입 서명에 문서화해야 하는 이펙트가 없습니다.
반면에 SendOrderAcknowledgement가 I/O를 수행할 것이라는 것을 알고 있으므로 비동기 이펙트가 필요합니다.
오류는 어떻습니까? 이 경우 우리는 오류 세부 사항에 신경 쓰지 않고 오류가 있더라도 무시하고 행복한 경로를 계속하고 싶습니다.
즉, 수정된 SendOrderAcknowledgement에는 Async 유형이 있지만 Result 유형은 없습니다.
(주 : SendResult를 체크해서 재전송하는 루틴을 반복할 뿐이며 오류를 보고하지 않습니다.)
type SendOrderAcknowledgment =
OrderAcknowledgment -> Async<SendResult>
type AcknowledgeOrder =
CreateOrderAcknowledgmentLetter // dependency
-> SendOrderAcknowledgment // Async dependency
-> PricedOrder // input
-> Async<OrderAcknowledgmentSent option> // Async output
'BackEnd' 카테고리의 다른 글
DDD 기능 구현을 위한 함수 이해하기 with F# (0) | 2022.03.19 |
---|---|
파이프라인으로 워크플로 모델링하기 - 워크플로 합성 및 나머지 (0) | 2022.03.19 |
파이프라인으로 워크플로 모델링하기 - 개요 및 상태 머신 (0) | 2022.03.19 |
도메인의 무결성(Integrity)과 일관성(Consistency) 관리하기 - Make illegal states unrepresentable (0) | 2022.03.19 |
타입으로 도메인 모델링하기 with F# - Aggregate (집합체) (0) | 2022.03.18 |