표현되는 모델
연관 관계
모델 내의 모든 탐색 가능한(traversable) 연관관계에 대해 그것과 동일한 특성을 지닌 메커니즘이 소프트웨어에도 있다.
연관관계를 좀더 쉽게 다루는 방법으로는 아래의 세 가지가 있다.
- 탐색 방향을 부여
- 한정자(qualifier)를 추가해서 사실상 다중성(multiplicity)을 줄인다.
- 중요하지 않은 연관관계를 제거한다.
가능한 한 관계를 제약하는 것이 중요하다. 양 방향 연관관계는 두 객체가 모두 있어야만 이해할 수 있다. 애플리케이션 요구사항에 두 방향을 모두 탐색해야 한다는 요건이 없을 경우 탐색 방향을 추가하면 상호의존성이 줄어들고 설계가 단순해진다. 그리고 도메인을 이해하면 도메인 본연의 방향성이 드러날지도 모른다.
이게 무슨 말이냐?
ENTITY
어떤 객체를 일차적으로 해당 객체의 식별성으로 정의할 경우 그 객체를 ENTITY라 한다.
ENTITY에는 모델링과 설계상의 특수한 고려사항이 포함돼 있다. ENTITY는 자신의 생명주기 동안 형태와 내용이 급격하게 바뀔 수도 있지만 연속성은 유지해야 한다. 또한 사실상 ENTITY를 추척하려면 ENTITY에 식별성이 정의돼 있어야 한다. ENTITY의 클래스 정의와 책임, 속성, 연관관계는 ENTITY에 포함된 특정 속성보다는 ENTITY의 정체성에 초점을 맞쳐야 한다. ENTITY가 그렇게까지 급격하게 변형되지 않거나 생명주기가 복잡하지 않더라도 의미에 따라 ENTITY를 분류한다면 모델이 더욱 투명해지고 구현은 견고해질 것이다.
-
한 객체가 속성보다는 식별성으로 구분될 경우 모델 내에서 이를 해당 객체의 주된 정의로 삼아라. (식별자?)
-
클래스 정의를 단순하게 하고 생명주기의 연속성과 식별성에 집중하라.
-
객체의 형태나 이력에 관계없이 각 객체를 구별하는 수단을 정의하라.
-
객체의 숙성으로 객체의 일치 여부를 판단하는 요구사항에 주의하라.
-
각 객체에 대해 유일한 결과를 반환하는 연산을 정의하라. 이러한 연산은 객체에 유일함을 보장받는 기호를 덧붙여서 정의할 수 있을지도 모른다. 이 같은 식별 수단은 외부에서 가져오거나 시스템에서 자체적으로 만들어 내는 임의의 식별자일 수도 있지만, 모델에서 식별성을 구분하는 방법과 일치해야한다. 모델은 동일하다는 것이 무슨 의미인지 정의해야 한다.
ENTITY는 생명주기 내내 이어지는 연속성과 애플리케이션 사용자에게 중요한 속성과는 독립적인 특정을 가짐.
객체가 가진 가진 식별성과 Entity가 가진 식별성은 다르다.
식별 메카니즘관점에서는 같을 수 있으나, ENTITY에서 말하는 식별성은 미묘하고 의미 있는 속성을 뜻함.
ENTITY 모델링
- 엔티티를 모델링할 때 속성에 관해 생각하는 것은 자연스러운 일
- 그러나, ENTITY의 가장 기본적인 책임은 객체의 행위가 명확하고 예측 가능해질 수 있게 연속성을 확립하는 것. 무슨말이니!?
- ENITITY의 속성이나 행위에 집중하기보다는 ENTITY객체를 해당 ENTITY객체의 가장 본질적인 특징(특히 해당 ENTITY를 식별하고 탐색하며 일치시키는 데 널리 사용되는)만으로 정의.
- 고객ID 는 Customer(고객)라는 ENTITY의 유일한 식별자. 가장 본질적인 특징인가?
ENTITY를 식별하고 탐색하며 일치시키는 데 널리 사용되는?
식별 연산의 설계
- 식별성에 대한 정의는 모델로부터 나온다. 따라서 식별성을 정의하려면 도메인을 이해해야 한다.
- “두 객체가 동일하다는 것이 무엇을 의미하는가?”
- ID를 지닌 각 객체를 표시하거나 두 인스턴스를 비교하는 연산을 작성하기는 쉽지만 이러한 ID나 연산이 도메인에 의미 있는 구분법에 부합하지 않는다면 문제를 더욱 혼란스럽게 만들 뿐이다. 이는 식별성 할당 연산에 간혹 사람이 개입할 때가 있기 때문이다.
VALUE OBJECT(값 객체)
- ENTITY의 식별성을 관리하는 일은 매우 중요하지만 그 밖의 객체에 식별성을 추가한다면 시스템의 성능이 저하되고, 분석 작업이 별도로 필요하며, 모든 객체를 동일한 것으로 보이게 해서 모델이 혼란스러워질 수 있다.
- 소프트웨어 설계는 복잡성과의 끊임없는 전투다. 그러므로 우리는 특별하게 다뤄야할 부분과 그렇지 않은 부분을 구분
- 하지만 이러한 범주에 속하는 객체를 단순히 식별성이 없는 것으로만 생각한다면 우리의 도구상자나 어휘에 추가할 게 그리 많지 않을 것. 사실 이 같은 객체는 자체적인 특징을 비롯해 모델에 중요한 의미를 지닌다. 이것들이 바로 사물을 서술하는 객체
- VALUE OBJECT는 설계 요소를 표현할 목적으로 인스턴스화되는데, 우리는 이러한 설계요소가 어느 것인지에 대해서는 관심이 없고 오직 해당 요소가 무엇인지에 대해서만 관심
복사와 공유 중 어느 것이 경제성 면에서 더 나은지는 구현 환경에 따라 달라짐.
복상의 경우 객체의 개수가 굉장히 많아져 시스템이 무거워질 수도 있지만 공유 또한 분산 시스템에서는 느려질 수 있음.
- 두 장비 간에 객체의 복사본을 전달되는 경우, 한 메세지가 전달되면 해당 메시지의 복사본은 메시지를 받는 쪽에 독립적으로 남음. 그러나 한 인스턴스를 공유하는 경우에는 객체 참조만 전달되므로 상호작용이 발생할 때마다 메시지가 해당 객체로 되돌아와야 한다.
SERVICE(서비스)
때때로 그것은 사물이 아닐 뿐이다..
설계가 매우 명확하고 실용적이라도 개념적으로 어떠한 객체에도 속하지 않는 연산이 포함될 때가 있다. 이러한 문제를 억지로 해결하려 하기보다는 문제 자체의 면면에 따라 SERVICE를 모델에 명시적으로 포함할 수 있다.
서비스의 정의가 뭘까?
Entity나 Value object 에서 찾지 못하는 중요한 도메인 연산이 이따. 이들 중 일부는 본질적으로 사물이 아닌 활동(activty)이나 행동(action)인데, 우리의 모델링 패러다임이 객체이므로 그러한 연산도 객체와 잘 어울리게 해야한다.
-
도메인의 개념 가운데 객체로는 모델에 어울리지 않는 것이 있다. 필요한 도메인 기능을 ENTITY나 VALUE에서 억지로 맡게 하면 모델에 기반을 둔 객체의 정의가 왜곡되거나, 또는 무의미하고 인위적으로 만들어진 객체가 추가될 것
-
SERIVCE는 모델에서 독립적인 인터페이스로 제공되는 연산으로서 ENTITY나 VALUE OBJECT와 달리 상태를 캡슐화하지 않는다. 기술적인 프레임워크에서는 SERVICE가 흔히 사용되는 패턴이지만 SERIVCE는 도메인 게층에도 마찬가지로 적용.
- 서비스 라는 이름은 다른 객체와의 관계를 강조.
- ENTITY나 VALUE OBJECT와 달리 SERVICE를 정의하는 기준은 순전히 클라이언트에 무엇을 제공할 수 있느냐? 에 따른다.
- ENTITY가 주로 동사나 명사로 이름을 부여하는 것과 달리 SERVICE는 주로 활동으로 이름을 지는다. 또한 SERVICE도 추상적이고 의도적인 정의를 가질 수 있으며, 이것은 객체 정의와는 특성이 다르다.
- 서비스의 매개변수와 결과는 도메인 객체여야 한다.
- 서비스는 적절히 사용해야 하고 ENTITY와 VALUE OBJECT의 행위를 모두 가져와서는 안 된다. 그러나 어떤 연산이 실제로 중요한 도메인 개념이라면 SERVICE는 MDD의 본연적인 부분을 형성하게 된다.
잘 만들어진 SERVICE는 세 가지 특징이 있다.
- 연산이 원래부터 ENTITY나 VALUE OBJECT의 일부를 구성하는 것이 아니라 도메인 개념과 관련돼 있다.
- 인터페이스가 도메인 모델의 외적 요소의 측면에서 정의
- 연산이 상태를 갖지 않는다.
도메인의 중대한 프로세스나 변환 과정이 ENTITY나 VALUE OBJECT의 고유한 책임이 아니라면 연산을 SERVICE로 선언되는 독립 인터페이스로 모델에 추가하라. 모델의 언어라는 측면에서 인터페이스를 정의하고 연산의 이름을 유비퀘터스 랭귀지의 일부가 되게끔 구성하라.서비스는 상태를 가지 않게 만들어라.
서비스와 격리된 도메인 계층
-
이 패턴이 도메인에서 중요한 의미를 지닌 SERVICE에 초점을 맞추곤 있지만, 물론 SERVICE는 도메인 계층에서만 이용되는 것이 아니다.
-
도메인 계층에 속하는 SERVICE와 다른 계층에 속하는 것들을 구분하고, 그러한 구분을 분명하게 유지하는 책임을 나누는데 주의를 기울여야 한다.
여전히 도메인 서비스와 응용서비스는 ENTITY와 VALUE을 구분한다는 것은 어떤 의미일까?
여기에서 우리는 도메인 계층과 응용 계층 사이에 놓인 아주 가느다란 경계선에 마주치게 된다.
예를 들면, 뱅킹 애플리케이션에서 거래를 분석할 수 있게 스프레드시트 파일로 거래내력을 반환해 내보낼 수 있다면 그러한 내보내기 기능은 응용서비스에 해당한다. 은행 업무도메인에서는 “파일 형식”이라는 것이 아무런 의미가 없으며, 그것과 관련된 어떠한 업무 규칙도 없기 때문이다.
한번 한 계좌에서 다른 계좌로 자금을 이체하는 기능은 도메인 서비스에 해당한다. 자금 이제 기능에는 중요한 업무 규칙(가령, 출금 계좌에서 자금을 인출해서 입금하는)이 포함돼 있고, “자금 이체”는 중요한 은행업무 도메인의 용어 이기 때문.
이 경우 서비스가 그 자체로는 많은 일을 하지 않으며, 두 ACCOUNT 객체가 대부분의 일을 수행하도록 요청할 것이다. 그러나 “이체”연산을 Account 객체에 집어넣는 것은 다소 부자연스러울 수도 있는데, 왜냐하면 이체 연산은 두 계좌와 일부 전역적인 규칙을 수반하기 때문이다.
서비스를 여러 계층으로 분할
응용 Service
- 자금 이체 응용 서비스
- 입력(XML 요청과 같은)내용의 암호화
- 이체 처리를 위한 도메인 서비스로의 메시지 전송
- 이체 확인 대기
- 인프라스트럭처 서비스를 이용한 통지 결정
도메인 Service
- 자금 이체 도메인 서비스
- 금액 인출/입금에 필요한 Account(계좌)와 Ledger(원장) 객체 간의 상호작용
- 이체 결과 확인 정보 제공(이체 수락 여부 등)
인프라스트럭처 Service
- 통지 서비스
- 애플리케이션에서 지정한 곳으로 이메일이나 우편 등을 보냄.
서비스에 접근
Service에 접근하는 수단이 특정 책임을 나누는 설계 의사결정만큼 중요하지는 않다. Service 인터페이스의 구현은 “행위자” 객체만으로도 충분할 수 있다. 간단한 싱글톤을 작성해 접근할 수도 있다.
MODULE(모듈, 패키지라고도 함)
모듈이 있는 이유는? 인지적 과부하(cognitive overload)
모듈을 두 가지 측면으로 바라볼 수 있다.
- 사람들은 전체에 압도되지 않고도 MODULE에 들어 있는 세부사항을 보거나,
- MODULE에 들어 있는 세부사항을 배제한 상태에서 MODULE간의 관계를 볼 수 있다.
모든 사람들이 MODULE을 사용하지만 그중에서 MODULE을 하나의 완전한 자격을 갖춘 모델 요소로 여기는 사람은 거의 없다. 코드는 기술적 아키텍처에서 개발자에게 할당된 작업까지 온갖 범주의 것으로 나뉜다. 그러나 리팩터링을 많이 하는 개발자도 프로젝트 초기에 생각해낸 모듈에 만족하는 경향이 있다.
MODULE간에는 결합도가 낮아야 하고 MODULE의 내부는 응집도가 높아야 한다는 것은 두말하면 잔소리다. 결합도와 응집도에 대한 설명은 그것을 기술적인 측정 기준처럼 들리게해서 연관관계와 상호작용의 배분 방법에 근거해 결합도와 응집도의 정도를 기계적으로 판단하게 만든다. 그러나 MODULE로 쪼개지는 것은 코드가 아닌 바로 개념이다.
어떤 사람이 한 번에 생각해낼 수 있는 한게가 있으며(따라서 결합도를 낮춰야 한다.), 일관성이 없는 단편적인 생각은 획일적인 생각을 섞어놓은 것처럼 이해하기 어렵다(따라서 응집도는 높여야 한다.)
도메인 주도 설계의 다른 모든 것들과 마찬가지로 MODULE로 하나의 의사소통 메커니즘이다. 우리는 분할되는 객체의 의미 에 따라 MODULE을 선택해야 한다. 어떤 클래스들을 한 MODULE 안에 함께 둔다면 그것은 바로 여러분 옆에서 설계를 살펴보는 동료 개발자에게 그 클래스들을 하나로 묶어서 생각하자고 말하는 것과 같다.
시스템의 내력을 말해주는 MODULE을 골라 일련의 응집력 있는 개념들을 해당 MODULE에 담아라. 이렇게 하면 종종 모듈 간의 결합도가 낮아지기도 하는데, 그렇게 되지 않는다면 모델을 변경해서 얽혀 있는 개념을 풀어낼 방법을 찾아보거나, 아니면 의미 있는 방식으로 모델의 각 요소를 맺어줄, MODULE의 기준이 될 법한 것 중 미처 못보고 지나친 개념을 찾아보자. 서로 독립적으로 이해하고 논리적으로 추론할 수 있다는 의미에서 낮은 결합도가 달성되도록 노력하라. 높은 수준의 도메인 개념에 따라 모델이 분리되고 그것에 대응되는 코드도 분리될 때까지 모델을 정제하라.
유비퀘터스랭귀지를 구성하는 것으로 MODULE의 이름을 부여하라. MODULE과 MODULE의 이름은 도메인에 통찰력을 줄 수 있어야 한다.
인프라스트럭쳐 주도 패키지화의 함정?
매우 유용한 프레임워크 표준의 예는 인프라 스트럭쳐 코드와 사용자 인터페이스 코드를 별도의 패키지 그룹에 두는 식으로 LAYERED ARCHITECTURE 를 적용해 도메인 계층을 물리적으로 자체적인 패키지 안으로 들어가게 하는 것이다.
단순하게 유지하는 편이 가장 좋다. 기술 환경에 필수적이거나 실질적으로 개발에 도움이 되는 최소한의 분할 규칙만 선택. 가령 복잡한 데이터 영속화 코드를 객체의 행위적인 측면에서 분리하면 리팩터링이 쉬워질 수도 있다.
여러 서버에 코드를 분산하는 것이 실제로 의도했던 바가 아니라면 동일한 객체는 아니더라도 하나의 개념적 객체를 구현하는 코드는 모두 같은 MODULE에 둬야 한다.
패키지화를 바탕으로 다른 코드로부터터 도메인 계층을 분리하라. 그렇게 할 수 없다면 가능한한 도메인 개발자가 자신의 모델과 설계 의사결정을 지원하는 형태로 도메인 객체를 자유로이 패키지화할 수 있게 하라.