domain-driven-design

05장. 소프트웨어에서 표현되는 모델

Introduction

연관관계

ENTITY (엔티티, 참조객체라고도 함)

ENTITY 모델링

식별 연산의 설계

VALUE OBJECT (값 객체)

VALUE OBJECT 의 설계

VALUE OBJECT 를 포함한 연관관계 설계

SERVICE (서비스)

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

오늘날 흔히 하는 실수는 행위를 적절한 객체로 다듬는 것을 너무나도 쉽게 포기해서 점점 절차적 프로그래밍에 빠지는 것이다. 객체의 정의에 어울리지 않는 연산을 강제로 객체에 포함시킨다면 해당 객체는 자신의 개념적 명확성을 잃어버리고 이해하거나 리팩터링하기 힘들어질 것이다.

이따금 서비스는 특정 연산을 수행하는 것 이상의 의미는 없는 모델 객체로 가장해서 나타나기도 한다. 행위자는 이름 끝에 Manager와 같은 것이 붙는다.

도메인의 개념 가운데 객체로는 모델에 어울리지 않는 것이 있다.

필요한 도메인 기능을 ENTITY나 VALUE에서 억지로 맡게 하면 모델에 기반을 둔 객체의 정의가 왜곡되거나, 또는 무의미하고 인위적으로 만들어진 객체가 추가될 것이다.

서비스라는 이름은 다른 객체와의 관계를 강조한다.

잘 만들어진 SERVICE의 특징

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

여기서 상태를 갖지 않는다는 것은 클라이언트가 특정 SERVICE 인스턴스의 개별 이력과는 상관없이 SERVICE의 모든 인스턴스를 사용할 수 있다는 의미다.

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

SEERVICE와 격리된 도메인 계층

수많은 도메인 SERVICE나 응용SERVICE는 ENTITY와 VALUE를 토대로 만들어져 도메인의 잠재 기능을 조직화 함으로써 실제로 뭔가가 이뤄지게 하는 시나리오와 같다.

뱅킹 애플리케이션에서 거래를 분석할 수 있게 스프레드시트 파일로 거래내역을 변환해 내보낼 수 있다면 내보내기 기능은 응용 SERVICE에 해당한다.

은행 업무 도메인에서는 파일 형식이라는 것이 아무런 의미가 없으며, 그것과 관련된 어떠한 업무 규칙도 없기 때문이다.

한 계좌에서 다른 계좌로 자금을 이체하는 기능은 도메인 SERVICE에 해당한다.

이체 기능에는 중요한 업무 규칙이 포함돼 있고, 이 경우 SERVICE가 그 자체로는 많은 일을 하지 않으며, 두 ACCOUNT 객체가 대부분의 일을 수행하도록 요청할 것이다.

또한 대부분의 개발 시스템에서는 도메인 객체와 외부 자원 간의 직접적인 인터페이스를 만든다는 것이 자연스러워 보이진 않는다. 우리는 그와 같은 외부 SERVICE를 모델의 측면에서 입력을 받아들이는 퍼사드로 만들 수 있으며, 아마도 퍼사드에서는 FUNDS TRANSFER객체를 결과로 반환할 것이다.

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

구성 단위

이 패턴은 ENTITY와 VALUE OBJECT로부터 클라이언트를 분리하는 것과 함께 도메인 계층의 인터페이스 구성 단위를 제어하는 수단으로서도 매우 가치가 있다.

구성 단위가 중간 크기인 무상태 SERVICE는 대형 시스템에서 재사용하기가 더 쉬울 수 있는데, 왜냐하면 그러한 서비스는 단순한 인터페이스 너머에 중요한 기능을 캡슐화하고 있기 때문이다.

구성 단위가 세밀한 객체는 분산 시스템에서 비효율적인 메시지 전송을 초래할 수 있다.

고도로 세분화된 상호작용의 복잡성이 결국 응용 계층에서 처리되고, 도메인 계층에서 사라진 도메인 지식이 응용 계층의 코드나 사용자 인터페이스 계층의 코드로 스며든다.

도메인 서비스를 적절히 도입하면 계층 간의 경계를 선명하게 하는데 도움될 수 있다.

클라이언트 제어와 융통성보다는 인터페이스의 단순함을 선호한다. 대형 시스템이나 분산 시스템에서 컴포넌트를 패키지화하는 데 매우 유용한 중간 구성 단위의 기능성을 제공한다. 그리고 때로는 SERVICE가 도메인 개념을 표현하는 가장 자연스러운 방법이기도 하다.

SERVICE에 접근하기

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

정교한 아키텍처는 시스템을 분산하거나 프레임워크의 기능을 활용하고자 하는 실제 요구가 있을 때만 사용해야 한다.

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

MODULE은 오래 전부터 확립되어 사용되고 있는 설계 요소다.

모듈화하는 가장 주된 이유는 바로 인지적 과부하 때문이다.

MODULE을 토대로 모델을 두 가지 측면에서 바라볼 수 있다.

도메인 계층의 MODULE은 모델의 중요한 요소로 나타나 도메인의 주요한 내력을 전해야만 한다.

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

MODULE간에는 결합도가 낮아야 하고, MODULE의 내부는 응집도가 높아야 한다는 것은 두말하면 잔소리다.

결합도와 응집도에 대한 설명은 그것을 기술적인 측정 기준처럼 들리게 해서 연관관계와 상호작용의 배분 방법에 근거해 결합도와 응집도의 정도를 기계적으로 판단하게 만든다.

MODULE로 쪼개지는 것은 코드가 아닌 바로 개념이다. 어떤 사람이 한 번에 생각해낼 수 양에는 한계가 있으며, 일관성이 없는 단편적인 생각은 획일적인 생각을 섞어놓은 것 처럼 이해하기 어렵다.

낮은 결합도와 높은 응집도는 개별 객체에서와 마찬가지로 MODULE에도 적용되는 일반적인 설계 원칙이며, 그 원칙은 구성 단위가 큰 모델링과 설계에서는 특히 중요하다.

두 모델 요소가 서로 다른 모듈로 분리될 때 마다 각 요소는 이전에 비해 좀더 간접적인 관계에 놓이는데, 이로써 설계에서 그와 같은 모델 요소의 위치를 파악하는 부담이 늘어난다.

잘 만들어진 모델 요소는 상승효과를 내며, 적절히 선택된 MODULE은 특별히 개념적 관계가 풍부한 모델 요소를 한 곳으로 모아주는 역할을 한다.

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

MODULE의 이름은 MODULE의 의미를 전해준다.

UBIQUITOUS LANGUAGE를 구성하는 것으로 MODULE의 이름을 부여하라. MODULE과 MODULE의 이름은 도메인에 통찰력을 줄 수 있어야 한다.

개념적 관계를 살펴보는 것이 기술적 측정의 대안은 아니다.

그러나 모델에 초점을 맞춰 사고하면 지엽적인 해결책이 아닌 더욱 심층적인 해결책이 만들어진다.

MODULE을 변경할 경우 각 MODULE을 더 많이 참조해야 하고 변경의 효과가 우발적으로 퍼져나가는 경우에도 마찬가지다. 모델이 일러주는 시스템의 이력을 개발자들이 이해한다면 개발자들은 이러한 문제를 다룰 수 있다.

기민한 MODULE

MODULE은 모델과 함께 발전해야 한다.

MODULE에 대한 리팩터링이 모델과 코드에 대한 리팩터링과 함께 일어난다는 것을 의미한다.

MODULE을 변경하려면 넓은 범위에 걸친 코드를 수정해야만 한다.

이 같은 변경은 팀의 의사소통을 해치고 심지어 소스코드 관리 시스템과 같은 개발 도구를 망칠 수도 있다.

MODULE의 구조와 이름이 현재 클래스가 나타내고 있는 것보다 훨씬 전의 초기 형태의 모델을 반영하기도 한다.

구현이 어떤 개발 기술을 기반으로 하느냐와 관계없이 MODULE을 리팩터링하는 데 소요되는 작업을 최소화하고 다른 개발자들과 의사소통할 때 발생하는 혼란을 최소화하는 방법을 모색해야 한다.

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

패키지화 결정의 주된 요인은 기술 관련 프레임워크에서 나온다.

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

티어 아키텍처는 모델 객체에 대한 구현을 잘게 나눠서 서로 흩어지게 할 수도 있다.

단일 도메인 객체의 책임을 여러 객체에 걸쳐 퍼뜨린 다음, 그러한 객체를 제각기 분리된 패키지에 두는 식으로 티어를 만들어 내기도 한다.

객체의 가장 기본적인 개념 중 하나는 데이터와 해당 데이터를 대상으로 연산을 수행하는 로직을 캡슐화하는 것이다.

이 외에도 프레임워크에서는 각 티어를 일련의 분리된 패키지들에 들어가게 하고 그러한 티어를 식별하는 규약에 따라 이름을 부여할 필요가 있었다.

애플리케이션에는 일부 SERVICE에서 제공하는 행위와 함께 데이터베이스 접근 요구사항만을 기본적으로 이행하는 빈약한 도메인 모델이 포함돼 있었다.

패키지화 계획을 해야 하는 또 다른 이유는 티어 배치 때문이다.

티어 배치는 코드가 실제로 서로 다른 서버에 배포돼 있을 경우 격렬한 논쟁거리가 될 수도 있다.

유연함이 필요한 경우에만 유연함을 추구하라.

기술적인 정교함이 주도하는 패키지화 계획에는 두 가지 비용이 따른다.

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

한 가지 예외적인 경우는 선언적 설계를 기반으로 코드가 만들어질 때다.

코드를 읽을 필요가 없으므로 생성되는 코드를 가까이에 있지 않게 별도의 패키지에 집어넣어 개발자가 실제로 작업해야 할 설계 요소를 어지럽히지 않는 게 좋다.

도메인 모델의 각 개념은 구현 요소에 반영돼야 한다. ENTITY와 VALUE OBJECT, 그리고 그러한 객체들 간의 연관관계는 일부 도메인 SERVICE와 조직화 MODULE과 함께 구현과 모델이 직접적으로 대응하는 지점이다.

어떤 것의 개념이 도메인 객체와 밀접하게 관련돼 있지 않다면, 그것을 도메인 객체에 추가해서는 안 된다.

수행해야 할 도메인 관련 책임과 시스템을 작동시키기 위해 관리해야 할 데이터가 있지만 그것들은 이러한 객체에 속하는 것이 아니다.

모델링 패러다임

MODEL-DRIVEN DESIGN은 현재 적용 중인 특정 모델링 패러다임과 조화를 이루는 구현 기술을 필요로 한다. 현재는 객체지향 설계가 가장 지배적인 패러다임이며, 오늘날 가장 복잡한 프로젝트에서도 객체를 사용하기 시작하고 있다.

객체지향 패러다임이 우세한 이유는 여러 가지가 있는데, 어떤 요인은 객체 본연의 특성에 기안하며, 또 어떤 것은 상황에 따라 달라지며, 그리고 그 밖에는 객체지향 패러다임이 널리 사용되고 있다는 이점에서 유래한다.

객체 패러다임이 지배적인 이유

모델링 패러다임이 너무도 난해해서 개발자들이 숙달할 수 없을 정도라면 개발자들은 그러한 모델링 패러다임을 서툴게 사용하게 될 것이다.

객체지향 설계 원리는 대부분의 사람들도 자연스럽게 이해하는 듯하다.

객체 모델링의 개념은 단순하지만 그것은 중요한 도메인 지식을 포착할 만큼 풍부한 것으로 입증됐다. 또한 객체 모델링은 처음부터 모델을 소프트웨어에서 표현하게 해주는 도구의 지원을 받고 있기도 하다.

성숙한 인프라스트럭처와 도구 지원이 없다면 프로젝트는 기술적인 연구 개발로 벗어나 지체되고, 애플리케이션을 개발하는 데 사용해야 할 자원을 전용해서 기술적인 위험에 처할 수 있다.

현재로서는 MODEL-DRIVEN DESIGN을 시도하는 대다수의 프로젝트에서는 시스템 기반에 객체지향 기술을 사용하는 것이 현명하다. MODEL-DRIVEN DESIGN을 시도하는 프로젝트는 객체 전용 시스템으로 고착화되지는 않을 것이다.

언제까지나 객체 패러다임만 써야 한다는 의미는 아니다. 객체 모델이 수많은 실무적인 소프트웨어 문제를 해결해주지만, 캡슐화된 행위에 대한 개별 묶음으로 모델링하기에는 수월하지 않은 도메인도 있다.

객체 세계에서 객체가 아닌 것들

도메인 모델이 반드시 객체 모델이어야 하는 것은 아니다.

여기서는 모델이 논리적인 규칙과 사실로 구성돼 있었다. 모델 패러다임은 사람들의 도메인에 관한 일정한 사고방식을 다루는 것으로 여겨져 왔다.

다소 극단적으로 말하면 도메인의 상당 부분이 특정한 다른 패러다임에서 훨씬 더 자연스럽게 표현된다면 패러다임을 완전히 교체해서 다른 구현 플랫폼을 선택하는 것이 맞을지도 모른다.

패러다임을 혼합하면 개발자들이 어떤 개념을 그것에 가장 잘 어울리는 형식으로 모델링할 수 있다.

개발자가 소프트웨어에 포함된 응집력 있는 모델을 분명하게 볼 수 없다면 이 같은 여러 패러다임이 혼재하는 시스템에서 MODEL-DRIVEN DESIGN의 필요성이 늘어나더라도 MODEL-DRIVEN DESIGN이 사라질 수 있다.

패러다임이 혼재할 때 MODEL-DRIVEN DESIGN 고수하기

룰 엔진은 때때로 객체지향 애플리케이션 개발 프로젝트에서 혼용되는 기술의 예로 알맞을 것이다.풍부한 지식이 담긴 도메인 모델에는 명시적인 규칙이 포함돼 있겠지만, 그럼에도 여전히 객체 패러다임에는 규칙과 규칙 간의 상호작용을 나타내기 위한 구체적인 의미체계가 부족하기 마련이다.

규칙을 다루는 동안에는 한 모델의 관점에서 사고를 지속하는 것이 중요하다.

팀에서는 두 가지 구현 패러다임에서 모두 작용할 수 있는 단 하나의 모델을 찾아야 한다.

각 부분을 함께 유지하는 가장 효과적인 수단은 완전히 이질적인 모델을 통합할 수 있는 확고한 UBIQUITOUS LANGUAGE다. UBIQUITOUS LANGUAGE에 포함된 명칭들을 두 환경에 일관되게 적용하고 활용한다면 두 환경 사이에 벌어진 틈을 메우는 데 도움될 것이다.

이것은 그 자체로도 한 권의 책으로 낼 만한 주제다.

MODEL-DRIVEN DESIGN이 객체지향적일 필요는 없지만 MODEL-DRIVEN DESIGN은 표현력이 풍부한 모델 구성물의 구현에 분명 의존한다.

객체가 아닌 요소를 객체지향 시스템에 혼합하는 데는 경험상 다음의 4가지 법칙이 있다.

관계형 패러다임은 패러다임 혼합의 특수한 경우다. 가장 흔히 접하는 비객체 기술인 관계형 데이터베이스는 다른 구성요소에 비해 객체 모델과 더욱 직접적인 관련이 있는데, 이것은 관계형 데이터베이스가 객체를 구성하는 데이터의 영구 저장소 역할을 하기 때문이다.