[DDD Start] 5. 표현되는 모델

표현되는 모델

연관 관계

모델 내의 모든 탐색 가능한(traversable) 연관관계에 대해 그것과 동일한 특성을 지닌 메커니즘이 소프트웨어에도 있다.

연관관계를 좀더 쉽게 다루는 방법으로는 아래의 세 가지가 있다.

  1. 탐색 방향을 부여
  2. 한정자(qualifier)를 추가해서 사실상 다중성(multiplicity)을 줄인다.
  3. 중요하지 않은 연관관계를 제거한다.

가능한 한 관계를 제약하는 것이 중요하다. 양 방향 연관관계는 두 객체가 모두 있어야만 이해할 수 있다. 애플리케이션 요구사항에 두 방향을 모두 탐색해야 한다는 요건이 없을 경우 탐색 방향을 추가하면 상호의존성이 줄어들고 설계가 단순해진다. 그리고 도메인을 이해하면 도메인 본연의 방향성이 드러날지도 모른다.

이게 무슨 말이냐?

image-20200326100836872

ENTITY

어떤 객체를 일차적으로 해당 객체의 식별성으로 정의할 경우 그 객체를 ENTITY라 한다.

ENTITY에는 모델링과 설계상의 특수한 고려사항이 포함돼 있다. ENTITY는 자신의 생명주기 동안 형태와 내용이 급격하게 바뀔 수도 있지만 연속성은 유지해야 한다. 또한 사실상 ENTITY를 추척하려면 ENTITY에 식별성이 정의돼 있어야 한다. ENTITY의 클래스 정의와 책임, 속성, 연관관계는 ENTITY에 포함된 특정 속성보다는 ENTITY의 정체성에 초점을 맞쳐야 한다. ENTITY가 그렇게까지 급격하게 변형되지 않거나 생명주기가 복잡하지 않더라도 의미에 따라 ENTITY를 분류한다면 모델이 더욱 투명해지고 구현은 견고해질 것이다.

ENTITY는 생명주기 내내 이어지는 연속성과 애플리케이션 사용자에게 중요한 속성과는 독립적인 특정을 가짐.

객체가 가진 가진 식별성과 Entity가 가진 식별성은 다르다.

식별 메카니즘관점에서는 같을 수 있으나, ENTITY에서 말하는 식별성은 미묘하고 의미 있는 속성을 뜻함.

ENTITY 모델링

식별 연산의 설계

VALUE OBJECT(값 객체)

복사와 공유 중 어느 것이 경제성 면에서 더 나은지는 구현 환경에 따라 달라짐.

복상의 경우 객체의 개수가 굉장히 많아져 시스템이 무거워질 수도 있지만 공유 또한 분산 시스템에서는 느려질 수 있음.

SERVICE(서비스)

때때로 그것은 사물이 아닐 뿐이다..

설계가 매우 명확하고 실용적이라도 개념적으로 어떠한 객체에도 속하지 않는 연산이 포함될 때가 있다. 이러한 문제를 억지로 해결하려 하기보다는 문제 자체의 면면에 따라 SERVICE를 모델에 명시적으로 포함할 수 있다.

서비스의 정의가 뭘까?

Entity나 Value object 에서 찾지 못하는 중요한 도메인 연산이 이따. 이들 중 일부는 본질적으로 사물이 아닌 활동(activty)이나 행동(action)인데, 우리의 모델링 패러다임이 객체이므로 그러한 연산도 객체와 잘 어울리게 해야한다.

잘 만들어진 SERVICE는 세 가지 특징이 있다.

  1. 연산이 원래부터 ENTITY나 VALUE OBJECT의 일부를 구성하는 것이 아니라 도메인 개념과 관련돼 있다.
  2. 인터페이스가 도메인 모델의 외적 요소의 측면에서 정의
  3. 연산이 상태를 갖지 않는다.

도메인의 중대한 프로세스나 변환 과정이 ENTITY나 VALUE OBJECT의 고유한 책임이 아니라면 연산을 SERVICE로 선언되는 독립 인터페이스로 모델에 추가하라. 모델의 언어라는 측면에서 인터페이스를 정의하고 연산의 이름을 유비퀘터스 랭귀지의 일부가 되게끔 구성하라.서비스는 상태를 가지 않게 만들어라.

서비스와 격리된 도메인 계층

여전히 도메인 서비스와 응용서비스는 ENTITY와 VALUE을 구분한다는 것은 어떤 의미일까?

여기에서 우리는 도메인 계층과 응용 계층 사이에 놓인 아주 가느다란 경계선에 마주치게 된다.

예를 들면, 뱅킹 애플리케이션에서 거래를 분석할 수 있게 스프레드시트 파일로 거래내력을 반환해 내보낼 수 있다면 그러한 내보내기 기능은 응용서비스에 해당한다. 은행 업무도메인에서는 “파일 형식”이라는 것이 아무런 의미가 없으며, 그것과 관련된 어떠한 업무 규칙도 없기 때문이다.

한번 한 계좌에서 다른 계좌로 자금을 이체하는 기능은 도메인 서비스에 해당한다. 자금 이제 기능에는 중요한 업무 규칙(가령, 출금 계좌에서 자금을 인출해서 입금하는)이 포함돼 있고, “자금 이체”는 중요한 은행업무 도메인의 용어 이기 때문.

이 경우 서비스가 그 자체로는 많은 일을 하지 않으며, 두 ACCOUNT 객체가 대부분의 일을 수행하도록 요청할 것이다. 그러나 “이체”연산을 Account 객체에 집어넣는 것은 다소 부자연스러울 수도 있는데, 왜냐하면 이체 연산은 두 계좌와 일부 전역적인 규칙을 수반하기 때문이다.

서비스를 여러 계층으로 분할

응용 Service

도메인 Service

인프라스트럭처 Service

서비스에 접근

Service에 접근하는 수단이 특정 책임을 나누는 설계 의사결정만큼 중요하지는 않다. Service 인터페이스의 구현은 “행위자” 객체만으로도 충분할 수 있다. 간단한 싱글톤을 작성해 접근할 수도 있다.

MODULE(모듈, 패키지라고도 함)

모듈이 있는 이유는? 인지적 과부하(cognitive overload)

모듈을 두 가지 측면으로 바라볼 수 있다.

  1. 사람들은 전체에 압도되지 않고도 MODULE에 들어 있는 세부사항을 보거나,
  2. MODULE에 들어 있는 세부사항을 배제한 상태에서 MODULE간의 관계를 볼 수 있다.

모든 사람들이 MODULE을 사용하지만 그중에서 MODULE을 하나의 완전한 자격을 갖춘 모델 요소로 여기는 사람은 거의 없다. 코드는 기술적 아키텍처에서 개발자에게 할당된 작업까지 온갖 범주의 것으로 나뉜다. 그러나 리팩터링을 많이 하는 개발자도 프로젝트 초기에 생각해낸 모듈에 만족하는 경향이 있다.

MODULE간에는 결합도가 낮아야 하고 MODULE의 내부는 응집도가 높아야 한다는 것은 두말하면 잔소리다. 결합도와 응집도에 대한 설명은 그것을 기술적인 측정 기준처럼 들리게해서 연관관계와 상호작용의 배분 방법에 근거해 결합도와 응집도의 정도를 기계적으로 판단하게 만든다. 그러나 MODULE로 쪼개지는 것은 코드가 아닌 바로 개념이다.

어떤 사람이 한 번에 생각해낼 수 있는 한게가 있으며(따라서 결합도를 낮춰야 한다.), 일관성이 없는 단편적인 생각은 획일적인 생각을 섞어놓은 것처럼 이해하기 어렵다(따라서 응집도는 높여야 한다.)

도메인 주도 설계의 다른 모든 것들과 마찬가지로 MODULE로 하나의 의사소통 메커니즘이다. 우리는 분할되는 객체의 의미 에 따라 MODULE을 선택해야 한다. 어떤 클래스들을 한 MODULE 안에 함께 둔다면 그것은 바로 여러분 옆에서 설계를 살펴보는 동료 개발자에게 그 클래스들을 하나로 묶어서 생각하자고 말하는 것과 같다.

시스템의 내력을 말해주는 MODULE을 골라 일련의 응집력 있는 개념들을 해당 MODULE에 담아라. 이렇게 하면 종종 모듈 간의 결합도가 낮아지기도 하는데, 그렇게 되지 않는다면 모델을 변경해서 얽혀 있는 개념을 풀어낼 방법을 찾아보거나, 아니면 의미 있는 방식으로 모델의 각 요소를 맺어줄, MODULE의 기준이 될 법한 것 중 미처 못보고 지나친 개념을 찾아보자. 서로 독립적으로 이해하고 논리적으로 추론할 수 있다는 의미에서 낮은 결합도가 달성되도록 노력하라. 높은 수준의 도메인 개념에 따라 모델이 분리되고 그것에 대응되는 코드도 분리될 때까지 모델을 정제하라.

유비퀘터스랭귀지를 구성하는 것으로 MODULE의 이름을 부여하라. MODULE과 MODULE의 이름은 도메인에 통찰력을 줄 수 있어야 한다.

인프라스트럭쳐 주도 패키지화의 함정?

매우 유용한 프레임워크 표준의 예는 인프라 스트럭쳐 코드와 사용자 인터페이스 코드를 별도의 패키지 그룹에 두는 식으로 LAYERED ARCHITECTURE 를 적용해 도메인 계층을 물리적으로 자체적인 패키지 안으로 들어가게 하는 것이다.

단순하게 유지하는 편이 가장 좋다. 기술 환경에 필수적이거나 실질적으로 개발에 도움이 되는 최소한의 분할 규칙만 선택. 가령 복잡한 데이터 영속화 코드를 객체의 행위적인 측면에서 분리하면 리팩터링이 쉬워질 수도 있다.

여러 서버에 코드를 분산하는 것이 실제로 의도했던 바가 아니라면 동일한 객체는 아니더라도 하나의 개념적 객체를 구현하는 코드는 모두 같은 MODULE에 둬야 한다.

패키지화를 바탕으로 다른 코드로부터터 도메인 계층을 분리하라. 그렇게 할 수 없다면 가능한한 도메인 개발자가 자신의 모델과 설계 의사결정을 지원하는 형태로 도메인 객체를 자유로이 패키지화할 수 있게 하라.