본문 바로가기
Concepts/SW Design

DDD - Assertion

by ocwokocw 2022. 10. 22.
- 출처: 도메인 주도 설계 - 에릭 에반스

- 개요

복잡한 계산을 하는 부분은 앞서 살펴본 side effect free function 으로 분리하면 문제의 난이도를 낮출 수 있다. 하지만 여전히 부수효과가 있는 명령(command)는 Entity 에 남아있으며 개발자는 이런 명령의 영향력을 이해해야 한다.
 
단순한 명령의 경우 코드를 조사하는것만으로도 결과를 예측할 수 있다. 하지만 앱의 규모가 커지면 작은 부분을 조합하여 큰 부분을 구성하는 설계가 등장하기 마련이고, 이럴 경우 사용자가 하나의 명령을 호출하면 다른 명령을 호출하게 된다. 이렇게 되면 각 호출되는 명령을 이해해야 해서 캡슐화가 깨지게 된다.
 
내부를 조사하지 않고 설계 요소의 의미와 연산 실행결과를 이해하는 방법이 필요하다. 이를 "단언"(Assertion)을 통해 달성할 수 있다. 단언은 2가지 구성요소로 이루어진다.
 
  • 사후조건: 연산의 부수효과, 호출되는 연산에서 보장하는 결과를 기술
  • 사전조건: 사후조건이 유효하기 위해 충족돼야 하는 조건들

- How to 

단언은 절차를 기술하는것이 아니라 상태만 기술한다.
 
연산의 사후 조건과 클래스 및 Aggregate의 불변식을 명시한다. 프로그래밍언어로 코드에 명시할 수 없다면 단위 테스트를 작성해서 표현한다.

- Example

Side effect free function 에서 예시로 사용한 페인트 혼합 예제를 이용하자.
 
해당 글에서 paint1.mixIn(paint2) 호출시 연산의 인자로 전달된 paint2의 상태가 모호하게 표현된다는 문제점을 언급한바있다. 만약 이런 연산을 실생활에서 수행한다면 paint1 은 용량이 늘어나는 대신 paint2 는 용량이 줄어들것이다. 그렇다면 paint2의 volume을 차감시켜야 할까?
 
실생활에서는 이런 모델에 맞지만 프로그램에서 인자의 상태를 변경하는건 굉장히 위험한 부수효과를 초래할 수 있는 행위이다. 
 
게다가 "나중에도 혼합된 페인트를 재현하기 위해 혼합한 페인트의 목록을 기록한다."와 같은 조건이 붙는다면 mixIn의 인자에 해당하는 페인트들의 양과 색상을 함부로 변경해서도 안된다.
 
실생활의 고증을 반영하여 인자도 전달된 페인트의 상태를 함부로 변경할수도, 페인트 혼합 내역을 위해서 페인트의 상태를 유지하기도 애매한 상황이 발생하는 이런 상황을 어떻게 해결해야할까?

- 개선

PigmentColor 개념을 새로 추출해냄으로써 Side effect free function을 이용하여 색상 계산을 수행할 수 있게 되었다. Example 단락의 마지막 상황에서 Paint는 2가지 책임을 갖고 있다.
 
하나는 혼합된 결과물을 보여주는것이고, 또 하나는 혼합된 결과물을 생성하기 위해 첨가되었던 페인트들을 기록하는것이다.
 
type paint interface {
	volume() float32
	color() pigmentColor
}

type stockPaint struct {
	volume float32
	color  pigmentColor
}

type mixedPaint struct {
	constituents []stockPaint
}

func (m *mixedPaint) volume() float32 {
	vol := float32(0)
	for _, c := range m.constituents {
		vol += c.volume
	}
	return vol
}

func (m *mixedPaint) color() pigmentColor {
	color := pigmentColor{}
	totVol := float32(0)
	for _, c := range m.constituents {
		ratio := totVol / c.volume
		color = color.mixInWith(c.color, ratio)
		totVol += c.volume
	}
	return color
}

func (m *mixedPaint) mixIn(other stockPaint) {
	m.constituents = append(m.constituents, other)
}

type pigmentColor struct {
	red, yellow, blue int
}

func (p pigmentColor) mixInWith(other pigmentColor, ratio float32) pigmentColor {
	// 새로운 빨강, 노랑, 파랑 값을 할당하는
	// 복잡한 색상 혼합 코드
	return pigmentColor{}
}
 
위의 코드는 크게 3 부분으로 구성되어 있다.
 
  • paint: 페인트를 본질적으로 표현하며 페인트의 양과 색상을 반환한다. 
  • mixedPaint: 첨가되었던 페인트를 알아내기 위해 stockPaint를 구성(composition) 하고 있다. mixIn 연산에서는 단순히 stockPaint를 추가한다. 만약 Client가 paint 인터페이스를 통해 양과 색상을 알아내고자 호출하면 갖고 있던 구성요소들을 연산하여 결과를 반환한다.
  • stockPaint: 첨가되었던 각 개별 페인트 구성요소를 나타낸다.
 
결과적으로 명령(command)는 mixIn 하나밖에 없으며 동작도 컬렉션에 객체를 추가할뿐 복잡한 연산을 부분이 모두 사라졌다.
 
 

'Concepts > SW Design' 카테고리의 다른 글

DDD - 개념적 윤곽  (0) 2022.10.31
DDD - Side effect free function  (0) 2022.10.22
DDD - Inteface  (0) 2022.10.01
DDD - Specification  (0) 2022.09.25
DDD - 불명확한 개념  (0) 2022.09.19

댓글