소스코드를 UML, 문서와 같이 사용할 수 있을까요?
F# 타입 시스템을 사용하여 코드에 대해 충분히 정확하게 도메인 모델을 캡처하면서
도메인 전문가 및 기타 비개발자가 읽고 이해할 수 있도록 하는 방법을 배웁니다.
타입이 대부분의 문서를 대체할 수 있으며 그 기능은 강력한 이점이 있음을 알 수 있습니다.
디자인이 코드 자체에 표현되기 때문에 구현이 설계와 반드시 동기화됩니다.
설계 리뷰하기
https://itchallenger.tistory.com/411
도메인을 문서화하기
기술적 구현에 대한 편견을 피하면서, 이러한 요구 사항을 어떻게 기록해야 할까요? 시각적 다이어그램(예: UML)을 사용할 수 있지만 작업하기 어렵고 도메인의 미묘한 부분을 포착할 만큼 충분
itchallenger.tistory.com
해당 모델을 코드로 변경해봅니다.
도메인 모델의 패턴 파악하기
- 단순한 값. 이들은 문자열 및 정수와 같은 기본 타입으로 표현되는 기본 빌딩 블록입니다. 그러나 실제로는 문자열이나 정수가 아닙니다. 도메인 전문가는 int 및 string의 관점에서 생각하지 않고 대신 OrderId 및 ProductCode의 관점에서 생각합니다. 이 개념은 유비쿼터스 언어의 일부입니다.
- AND와 값의 조합. 밀접하게 연결된 데이터 그룹입니다. 페이퍼 기반 업무를 수행하는 현실 세계에서 이들은 일반적으로 문서 또는 문서의 하위 구성요소(이름, 주소, 주문 등)입니다.
- OR이 있는 초이스. Order 또는 Quote, UnitQuantity 또는 KilogramQuantity와 같이 도메인에서 선택을 나타내는 항목이 있습니다.
- 워크플로. 마지막으로 입력과 출력이 있는 비즈니스 프로세스가 있습니다.
간단한 값 모델링
type CustomerId =
| CustomerId of int
케이스가 하나뿐이므로 다음과 같이 항상 전체 유형 정의를 한 줄에 작성합니다.
type CustomerId = CustomerId of int
type WidgetCode = WidgetCode of string
type UnitQuantity = UnitQuantity of int
type KilogramQuantity = KilogramQuantity of decimal
type CustomerId = CustomerId of int
// ^type name ^case label
(하스켈의 data 키워드와 비슷한데, wrapper type의 케이스가 type 키워드로 사용된다는 것이 차이점)
단일 케이스 유니온 사용하기
type CustomerId = CustomerId of int
// ^this case name will be the constructor function
let customerId = CustomerId 42
// ^this is a function with an int parameter
// define a function using a CustomerId
let processCustomerId (id:CustomerId) = ...
// call it with an OrderId -- compiler error!
processCustomerId orderId
// ^ This expression was expected to
// have type 'CustomerId' but here has
// type 'OrderId'
// construct
let customerId = CustomerId 42
// deconstruct
let (CustomerId innerValue) = customerId
// ^ innerValue is set to 42
printfn "%i" innerValue // prints "42"
// deconstruct
let processCustomerId (CustomerId innerValue) =
printfn "innerValue is %i" innerValue
// function signature
// val processCustomerId: CustomerId -> unit
Constrained Value
Avoiding Performance Issues with Simple Types
type UnitQuantity = int
[<Struct>]
type UnitQuantity = UnitQuantity of int
type UnitQuantities = UnitQuantities of int[]
Modeling Complex Data
Modeling with Record Types
data Order =
CustomerInfo
AND ShippingAddress
AND BillingAddress
AND list of OrderLines
AND AmountToBill
type Order = {
CustomerInfo : CustomerInfo
ShippingAddress : ShippingAddress
BillingAddress : BillingAddress
OrderLines : OrderLine list
AmountToBill : ...
}
Modeling Unknown Types
(타입스크립트 type Undefined = Error)
type Undefined = exn
type CustomerInfo = Undefined
type ShippingAddress = Undefined
type BillingAddress = Undefined
type OrderLine = Undefined
type BillingAmount = Undefined
type Order = {
CustomerInfo : CustomerInfo
ShippingAddress : ShippingAddress
BillingAddress : BillingAddress
OrderLines : OrderLine list
AmountToBill : BillingAmount
}
Modeling with Choice Types
data ProductCode =
WidgetCode
OR GizmoCode
data OrderQuantity =
UnitQuantity
OR KilogramQuantity
F#에선 Choice Type을 이용해 해당 선택을 명시화 합니다.
type ProductCode =
| Widget of WidgetCode
| Gizmo of GizmoCode
type OrderQuantity =
| Unit of UnitQuantity
| Kilogram of KilogramQuantity
Modeling Workflows with Functions
type ValidateOrder = UnvalidatedOrder-> ValidatedOrder
복잡한 입출력 작업
type PlaceOrderEvents = {
AcknowledgmentSent : AcknowledgmentSent
OrderPlaced : OrderPlaced
BillableOrderPlaced : BillableOrderPlaced
}
이 접근 방식을 사용하면 Primitive UnvalidatedOrder를 입력으로 시작하고 PlaceOrderEvents 레코드를 반환하는 함수 타입으로 Order-Placing 워크플로를 작성할 수 있습니다.
type PlaceOrder = UnvalidatedOrder -> PlaceOrderEvents
workflow "Categorize Inbound Mail" =
input: Envelope contents
output:
QuoteForm (put on appropriate pile)
OR OrderForm (put on appropriate pile)
OR ...
type EnvelopeContents = EnvelopeContents of string
type CategorizedMail =
| Quote of QuoteForm
| Order of OrderForm
// etc
type CategorizeInboundMail = EnvelopeContents -> CategorizedMail
"Calculate Prices" =
input: OrderForm, ProductCatalog
output: PricedOrder
type CalculatePrices = OrderForm -> ProductCatalog -> PricedOrder
type CalculatePricesInput = {
OrderForm : OrderForm
ProductCatalog : ProductCatalog
}
이제 함수 타입은 다옴과 같습니다.
type CalculatePrices = CalculatePricesInput -> PricedOrder
Documenting Effects in the Function Signature
함수형 프로그래밍에선, 기본 출력 외에 함수가 수행하는 작업을 설명하기 위해 Effect라는 용어를 사용합니다.
type ValidateOrder = UnvalidatedOrder -> ValidatedOrder
type ValidateOrder =
UnvalidatedOrder -> Result<ValidatedOrder,ValidationError list>
and ValidationError = {
FieldName : string
ErrorDescription : string
}
함수형 프로그래밍에선, 기본 출력 외에 함수가 수행하는 작업을 설명하기 위해 효과라는 용어를 사용합니다. 여기에서 Result를 사용하여 ValidateOrder에 "error Effect"가 있을 수 있음을 문서화했습니다. 이것은 함수가 항상 성공할 것이라고 가정할 수 없으며 오류를 처리할 준비가 되어 있어야 한다는 것을 타입 시그니처에서 분명히 합니다.
type ValidateOrder =
UnvalidatedOrder -> Async<Result<ValidatedOrder,ValidationError list>>
type ValidationResponse<'a> = Async<Result<'a,ValidationError list>>
type ValidateOrder =
UnvalidatedOrder -> ValidationResponse<ValidatedOrder>
'BackEnd' 카테고리의 다른 글
타입으로 도메인 모델링하기 with F# - Aggregate (집합체) (0) | 2022.03.18 |
---|---|
타입으로 도메인 모델링하기 with F# - Value Object, Entitiy (0) | 2022.03.18 |
타입으로 코드 문서화하기 With F# - 타입을 조합하여 도메인 모델링 (0) | 2022.03.18 |
타입으로 코드 문서화하기 With F# - 타입 기초 (0) | 2022.03.18 |
도메인 모델을 함수형 아키텍처로 - 컨텍스트 간 통신 2 (0) | 2022.03.17 |