본문 바로가기

BackEnd

도메인을 문서화하기

반응형
기술적 구현에 대한 편견을 피하면서, 이러한 요구 사항을 어떻게 기록해야 할까요?
 
시각적 다이어그램(예: UML)을 사용할 수 있지만 작업하기 어렵고
도메인의 미묘한 부분을 포착할 만큼 충분히 상세하지 않은 경우가 많습니다.
 

 

도메인 모델을 코드로 작성하기 전에,
지금은 도메인 모델을 캡처하는 데 사용할 수 있는 간단한 텍스트 기반 언어를 만들어 보겠습니다.



  • 워크플로의 경우
    • 입력과 출력을 문서화한 다음 비즈니스 논리에 간단한 의사 코드를 사용합니다.
​ 	Bounded context: Order-Taking
​ 	
​ 	Workflow: "Place order"
​ 	   triggered by:
​ 	      "Order form received" event (when Quote is not checked)
​ 	   primary input:
​ 	      An order form
​ 	   other input:
​ 	      Product catalog
​ 	   output events:
​ 	      "Order Placed" event
​ 	   side-effects:
​ 	      An acknowledgment is sent to the customer,
​ 	      along with the placed order
  • 데이터 구조의 경우
    • AND를 사용하여 Name AND Address와 같이 두 부분이 모두 필요함을 의미합니다.
    • OR을 사용하여 Email 또는 PhoneNumber와 같이 어느 한 부분이 필수임을 의미합니다.
​ 	bounded context: Order-Taking
​ 	
​ 	data Order =
​ 	    CustomerInfo
​ 	    AND ShippingAddress
​ 	    AND BillingAddress
​ 	    AND list of OrderLines
​ 	    AND AmountToBill
​ 	
​ 	data OrderLine =
​ 	    Product
​ 	    AND Quantity
​ 	    AND Price
​ 	
​ 	data CustomerInfo = ???   // don't know yet
​ 	data BillingAddress = ??? // don't know yet
우리는 클래스 계층이나 데이터베이스 테이블 등을 만들려고 시도하지 않았습니다.
약간 구조화된 방식으로 도메인을 캡처하려고 했습니다.
 
이런 종류의 텍스트 기반 디자인의 장점은 프로그래머가 아닌 사람들에게 무섭지 않다는 것입니다.
즉, 도메인 전문가에게 보여주고 함께 작업할 수 있다는 의미입니다.
엔지니어들은 기술적인 문제에 집중하고 모든 요구 사항을 동등하게 취급하는 경향이 있습니다.
기업은 그렇게 생각하지 않습니다. 돈을 버는 것(또는 돈을 절약하는 것)은 거의 항상 개발 프로젝트의 원동력입니다.

도메인 모델의 복잡성을 나타내기

워크플로는 여러개의 단계로 나누어 질 수 있습니다.

또한 종속성(업무적인 - 다른 바운디드 컨텍스트에 의존)이 코드에서의 의존성(ex 의존성 주입 대상)이 된다는 것도 알아둡시다.

지금까지 이해한 도메인. 사이드이펙트와 의존성을 파악하기

 이 도표는 우리가 배운 모든 것을 반영하지 않습니다. 텍스트 기반 언어로 이 모든 새로운 정보를 더 잘하고 캡처할 수 있는지 봅시다.

 

제약 사항 나타내기

제품 코드와 수량은 단순한 문자열과 정수가 아니라 다양한 방식으로 제한됩니다.

​ 	context: Order-Taking
​ 	
​ 	data WidgetCode = string starting with "W" then 4 digits
​ 	data GizmoCode = string starting with "G" then 3 digits
​ 	data ProductCode = WidgetCode OR GizmoCode
설계가 엄격하다고 해서 구현이 엄격해야 하는 것은 아님을 기억하십시오.
예를 들어, 검증 프로세스의 자동화된 버전은 전체 주문을 완전히 거부하는 대신 사람이 승인하도록
이상한 코드에 플래그를 지정할 수 있습니다.
이제 수량에 대한 요구 사항을 문서화하는 것은 어떻습니까? 제안된 디자인은 다음과 같습니다.
​ 	data OrderQuantity = UnitQuantity OR KilogramQuantity
​ 	
​ 	data UnitQuantity = integer between 1 and ?
​ 	data KilogramQuantity = decimal between ? and ?

해당 요구사항을 문서화하면서 생각해봅시다. 정말 상한이 없을까요?

이러한 종류의 제약 조건은 포착하는 데 중요합니다.

우리는 생산에서 단위가 음수가 되거나 무게가 수백 킬로톤인 상황을 원하지 않습니다.

다음은 이러한 제약 조건이 문서화된 업데이트된 사양입니다.

​ 	data UnitQuantity = integer between 1 and 1000
​ 	data KilogramQuantity = decimal between 0.05 and 100.00

데이터(Order)의 생명 주기 나타내기

이제 Order로 넘어갑시다. 이전 디자인 스케치에서 Order에 대한 간단한 정의가 있었습니다.
​ 	data Order =
​ 	    CustomerInfo
​ 	    AND ShippingAddress
​ 	    AND BillingAddress
​ 	    AND list of OrderLines
​ 	    AND AmountToBill
주문에는 수명 주기가 있습니다. 검증되지 않은 상태(우편에서 직접)로 시작한 다음 "검증된" 다음 "가격이 책정"됩니다.
처음에는 주문에 가격이 없지만 결국에는 가격이 있습니다. 위의 간단한 Order 정의는 그 구분을 지웁니다.
(입력데이터를 통해 계산합니다.)

우리는 도메인 모델에서 이러한 단계를 캡처해야 합니다.

문서화뿐만 아니라 (예를 들어) 가격이 책정되지 않은 주문이 배송 부서로 보내져서는 안 된다는 점을 분명히 하기 위해서입니다.


가장 쉬운 방법은 각 단계에 대해 Unvalidated-Order, ValidatedOrder 등의 새 이름을 만드는 것입니다.
그것은 디자인이 작성이 더 길고 더 지루하다는 것을 의미하지만 장점은 모든 것이 명확해진다는 것입니다.
 	data UnvalidatedOrder =
​ 	    UnvalidatedCustomerInfo
​ 	    AND UnvalidatedShippingAddress
​ 	    AND UnvalidatedBillingAddress
​ 	    AND list of UnvalidatedOrderLine
​ 	
​ 	data UnvalidatedOrderLine =
​ 	    UnvalidatedProductCode
​ 	    AND UnvalidatedOrderQuantity
워크플로 시작 시 CustomerInfo가 아직 검증되지 않았으며
ShippingAddress가 아직 검증되지 않았음이 명시되어 있습니다.

 

검증된 주문은 다음과 같이 문서화할 수 있습니다.
​ 	data ValidatedOrder =
​ 	    ValidatedCustomerInfo
​ 	    AND ValidatedShippingAddress
​ 	    AND ValidatedBillingAddress
​ 	    AND list of ValidatedOrderLine
​ 	
​ 	data ValidatedOrderLine =
​ 	    ValidatedProductCode
​ 	    AND ValidatedOrderQuantity
이것은 모든 구성 요소가 이제 확인되었으며 유효함을 나타냅니다.
다음 단계는 주문 가격을 책정하는 것입니다. PricedOrder은 다음을 제외하고는 validated order과 같습니다.
  • 각 라인에는 연관된 가격이 있습니다. 즉, PricedOrderLine은 ValidatedOrderLine에 LinePrice를 더한 것입니다.
  • 전체 주문에는 라인 가격의 합계로 계산된 AmountToBill이 연결되어 있습니다.
​ 	data PricedOrder =
​ 	    ValidatedCustomerInfo
​ 	    AND ValidatedShippingAddress
​ 	    AND ValidatedBillingAddress
​ 	    AND list of PricedOrderLine  // different from ValidatedOrderLine
​ 	    AND AmountToBill             // new
​ 	
​ 	data PricedOrderLine =
​ 	    ValidatedOrderLine
​ 	    AND LinePrice                // new
마지막 단계는 주문 승인을 생성하는 것입니다.
​ 	data PlacedOrderAcknowledgment =
​ 	    PricedOrder
​ 	    AND AcknowledgmentLetter

위 디자인에서 다음과 같은 내용을 포착할 수 있습니다.

  • 검증되지 않은 주문은 가격 정보가 없습니다.
  • validated order 모든 라인이 검증되어야 합니다.

모델이 너무 복잡하다 생각할 수 있으나, 이 정도로 풍부하지 않았다면 요구사항을 포착하지 못했을 것입니다.

이제 코드에서도 이러한 개념을 유지할 수 있다면 코드가 도메인을 정확하게 반영하고 적절한 "도메인 기반" 디자인을 갖게 됩니다.
 
워크플로의 단계 구체화
 
워크플로는 검증, 가격 책정 등의 더 작은 단계로 나눌 수 있습니다. 이러한 각 단계에 동일한 입력/출력 접근 방식을 적용해 보겠습니다.

첫째, 전체 워크플로의 출력이 이전에 생각했던 것보다 조금 더 복잡합니다. 원래 유일한 출력은 "주문 완료(Order placed)" 이벤트였지만 이제 워크플로의 가능한 결과는 다음과 같습니다.

  • “Order placed” event to shipping/billing
  • 잘못된 주문 더미(목록함)에 주문 양식을 추가하고 나머지 단계를 건너뜁니다. - 에러처리
 
ValidateOrder 단계를 별도의 하위 단계로 나누어 전체 워크플로를 의사 코드로 문서화해 보겠습니다.
 
첫번째 서브스텝에서는 에러 핸들링을 하고 있는 것에 주목합니다.
​ 	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)

각각의 서브스텝은 입출력을 갖고 있습니다.

의존성을 따로 확인합니다.

 

주문 유효성 검사 서브스텝입니다. 오류가 발생할 수 있습니다.

​ 	substep "ValidateOrder" =
​ 	    input: UnvalidatedOrder
​ 	    output: ValidatedOrder OR ValidationError
​ 	    dependencies: CheckProductCodeExists, CheckAddressExists
​ 	
​ 	    validate the customer name
​ 	    check that the shipping and billing address exist
​ 	    for each line:
​ 	        check product code syntax
​ 	        check that product code exists in ProductCatalog
​ 	
​ 	    if everything is OK, then:
​ 	        return ValidatedOrder
​ 	    else:
​ 	        return ValidationError

주문 가격 계산 서브스텝입니다.

 	substep "PriceOrder" =
​ 	    input: ValidatedOrder
​ 	    output: PricedOrder
​ 	    dependencies: GetProductPrice
​ 	
​ 	    for each line:
​ 	        get the price for the product
​ 	        set the price for the line
​ 	    set the amount to bill ( = sum of the line prices)
마지막으로 마지막 하위 단계는 PricedOrder를 입력으로 받은 다음 승인을 생성하여 보냅니다.
​ 	substep "SendAcknowledgmentToCustomer" =
​ 	    input: PricedOrder
​ 	    output: None
​ 	
​ 	    create acknowledgment letter and send it
​ 	    and the priced order to the customer
요구 사항에 대한 이 문서는 현재 코드와 훨씬 유사해 보이지만 도메인 전문가가 여전히 읽고 확인할 수 있습니다.

 

> 커맨드 타입 정의 보기

https://itchallenger.tistory.com/421?category=1086398 

 

파이프라인으로 워크플로 모델링하기 - 개요 및 상태 머신

이전 두 장에서 타입을 사용하여 일반적인 방식으로 도메인 모델링을 수행하는 방법을 보았습니다. 이 장에서는 거기서 배운 내용을 주문 처리(order-placing ) 워크플로에 적용할 것입니다. 그 과

itchallenger.tistory.com

 

반응형