Skip to content

Latest commit

 

History

History

chapter06

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

06장. 도메인 객체의 생명주기

Introduction

  • 모든 객체에는 생명주기가 있다.
  • 한 객체는 생성되어 다양한 상태를 거친 후 결국 저장되거나 삭제되면서 소멸함
  • 도메인 객체의 관리와 관련된 문제는 아래의 두 가지 범주
    • 생명주기 동안의 무결성 유지하기
    • 생명주기 관리의 복잡성으로 모델이 난해해지는 것을 방지하기
  • 본 장에서는 이러한 문제를 세 가지 패턴을 이용해 해결
    • AGGREGATE (집합체)
      • 소유권과 경계를 명확히 정의함으로써 모델을 엄격하게 만들어 객체 간의 연관관계가 혼란스럽게 얽히지 않게 한다.
      • 이 패턴은 생명주기상의 전 단계에 걸쳐 도메인 객체의 무결성을 유지하는데 매우 중요
    • FACTORY (팩터리)
      • 복잡한 객체와 AGGREGATE를 생성하고 재구성함으로써 그것들의 내부 구조를 캡슐화하는 것에 관해 살펴봄
    • REPOSITORY (리파지터리)
      • 생명주기의 중간과 마지막을 다루며, 거대한 관련 인프라스트럭처를 캡슐화하면서 영속 객체를 찾아 조회하는 수단을 제공함
  • REPOSITORY 와 FACTORY 가 도메인에서 나오는 것은 아니지만, 그것들은 도메인 설계에서 중요한 역할을 담당함
  • 이러한 구성물은 모델 객체에 쉽게 접근하는 수단을 제공해 MODEL-DRIVEN DESIGN을 완성함

AGGREGATE (집합체)

  • 모델 내에서 복잡한 연관관계를 맺는 객체를 대상으로 변경의 일관성을 보장하기란 쉽지 않다.
  • 그 까닭은 단지 개별 객체만이 아닌 서로 밀접한 관계에 있는 객체 집합에도 불변식이 적용돼야 하기 때문
  • 이러한 문제가 데이터베이스 트랜잭션과 관련된 기술적 문제로 나타나더라도 문제의 근원은 모델에 경계가 정의돼 있지 않다는 데 있다.
  • 모델을 근간으로 하는 해법을 이용하면 모델을 좀 더 이해하기 쉬워지고 설계한 바가 더 쉽게 전달될 것
  • 먼저 우리는 모델 내의 참조에 대한 캡슐화를 추상화할 필요가 있다.
  • AGGREGATE 는 우리가 데이터 변경의 단위로 다루는 연관 객체의 묶음
  • 각 AGGREGATE 에는 루트(root)와 경계(boundary)가 있다.
  • 경계
    • AGGREGATE에 무엇이 포함되고 포함되지 않는지를 정의
  • 루트
    • 단 하나만 존재
    • AGGREGATE에 포함된 특정 ENTITY를 가리킴
  • 경계 안의 객체는 서로 참조할 수 있음
  • 경계 바깥의 객체는 해당 AGGREGATE의 구성요소 가운데 루트만 참조할 수 있음
  • 루트 이외의 ENTITY는 지역 식별성(local identity)을 지니며, 지역 식별성은 AGGREGATE 내에서만 구분되면 됨
  • 해당 AGGREGATE 의 경계 밖에 위치한 객체는 루트 ENTITY 의 컨텍스트 말고는 AGGREGATE 의 내부를 볼 수 없기 때문
  • 불변식은 데이터가 변경될 때마다 유지돼야 하는 일관성 규칙을 뜻하며, 여기엔 AGGREGATE를 구성하는 각 구성요소 간의 관계도 포함될 것
  • 한 AGGREGATE에 적용된 불변식은 각 트랜잭션이 완료될 때 이행될 것
  • 개념적 AGGREGATE를 구현하려면 모든 트랜잭션에 적용되는 다음과 같은 규칙이 필요
    • 루트 ENTITY는 전역 식별성을 지니며 궁극적으로 불변식을 검사할 책임이 있다.
    • 각 루트 ENTITY는 전역 식별성을 지니고, 경계 안의 ENTITY는 지역 식별성을 지니며, 지역 식별성은 해당 AGGREGATE 안에서만 유일
    • AGGREGATE의 경계 밖에서는 루트 ENTITY를 제외한 AGGREGATE 내부의 구성요소를 참조할 수 없다.
      • 루트는 VALUE OBJECT의 복사본을 다른 객체에 전달해 줄 수 있으며, 복사본에서는 어떤 일이 일어나든 문제되지 않는다.
    • 데이터베이스 질의를 이용하면 AGGREGATE의 루트만 직접적으로 획득할 수 있다.
      • 다른 객체는 모두 AGGREGATE 를 탐색해서 발견해야 한다.
    • AGGREGATE 안의 객체는 다른 AGGREGATE의 루트만 참조할 수 있다.
    • 삭제 연산은 AGGREGATE 경계 안의 모든 요소를 한 번에 제거해야 한다.
    • AGGREGATE 경계 안의 어떤 객체를 변경하더라도 전체 AGGREGATE의 불변식은 모두 지켜져야 한다.
  • ENTITY와 VALUE OBJECT를 AGGREGATE로 모으고 각각에 대해 경계를 정의하라.
  • 한 ENTITY를 골라 AGGREGATE의 루트로 만들고, AGGREGATE 경계 내부의 객체에 대해서는 루트를 거쳐 접근할 수 있게 하라.
  • AGGREGATE 밖의 객체는 루트만 참조할 수 있게 하라.
  • 루트를 경유하지 않고는 AGGREGATE의 내부를 변경할 수 없다.
  • 이런 식으로 AGGREGATE의 각 요소를 배치하면 AGGREGATE 안의 객체와 전체로서의 AGGREGATE의 상태를 변경할 때 모든 불변식을 효과적으로 이행할 수 있다.
  • AGGREGATE를 선언하고 잠금 기법 등을 자동으로 수행하게 하는 데 기술 관련 프레임워크가 매우 유용할 수 있다.
  • AGGREGATE는 생명주기의 전 단계에서 불변식이 유지돼야 할 범위를 표시해준다.
  • FACTORY와 REPOSITORY는 AGGREGATE를 대상으로 연산을 수행하며, 특정 생명 주기 전이에 따르는 복잡성을 캡슐화함

FACTORY (팩터리)

  • 어떤 객체나 전체 AGGREGATE를 생성하는 일이 복잡해지거나 내부 구조를 너무 많이 드러내는 경우 FACTORY가 캡슐화를 제공
  • 객체는 그것의 존재 이유와 관련이 없거나 다른 객체와 상호작용할 때 해당 객체의 역할을 보조하지 않는 것이 아무것도 남지 않을 때까지 정제해야 하낟.
  • 이러한 객체의 책임 중에는 전체 생명 주기의 중간 단계에서 수행해야 하는 것들이 많다.
  • 문제는 이러한 책임만으로도 복잡한 객체에 객체 자체를 생성하는 책임까지 맡기는 데 있다.
  • 그러나 애플리케이션에서 그와 같은 책임을 클라이언트 객체로 옮긴다면 문제가 훨씬 더 나빠진다.
  • 객체 생성을 맡은 클라이언트는 불필요하게 복잡해지고 클라이언트가 맡고 있는 책임은 불분명해진다.
  • 그 클라이언트는 도메인 객체와 생성된 AGGREGATE의 캡슐화를 위반한다.
  • 더욱 안 좋은 점은 그 클라이언트가 응용 계층의 일부를 구성하고 있다면 도메인 계층에서 책임이 새어 나온다는 것이다.
  • 어떤 객체를 생성한다는 것이 그 자체로도 주요한 연산이 될 수 있지만, 복잡한 조립 연산은 생성된 객체의 책임으로 어울리지 않는다.
  • 클라이언트에서 직접 필요로 하는 객체를 생성하면 클라이언트 설계가 지저분해지고 조립되는 객체나 AGGREGATE의 캡슐화를 위반하며, 클라이언트와 생성된 객체의 구현이 지나치게 결합된다.
  • 복잡한 객체를 생성하는 일은 도메인 계층의 책임이지만, 그것이 모델을 표현하는 객체에 속하는 것은 아니다.
  • 일반적으로 객체의 생성과 조립은 도메인에서는 아무런 의미가 없지만 구현 측면에서는 반드시 필요하다.
  • 즉, 우리가 모델 내의 어떤 것에도 해당하지 않는 요소를 설계에 추가하는 것이긴 하지만, 그럼에도 그러한 요소는 도메인 계층에서 맡고 있는 책임의 일부를 구성한다는 것이다.
  • 자신의 책임이 다른 객체를 생성하는 것인 프로그램 요소를 FACTORY라 한다.
  • FACTORY는 복잡한 객체나 AGGREGATE를 생성하는 데 필요한 지식을 캡슐화한다.
  • FACTORY는 클라이언트의 목적과 생성된 객체의 추상적인 관점을 반영하는 인터페이스를 제공한다.
  • 복잡한 객체와 AGGREGATE의 인스턴스를 생성하는 책임을 별도의 객체로 옮겨라.
  • 모든 복잡한 객체 조립 과정을 캡슐화하는 동시에 클라이언트가 인스턴스화되는 객체의 구상 클래스를 참조할 필요가 없는 인터페이스를 제공하라.
  • 전체 AGGREGATE를 하나의 단위로 생성해서 그것의 불변식이 이행되게 하라.
  • FACTORY를 잘 설계하기 위한 두 가지 기본 요건
    • 각 생성 방법은 원자적(atomic)이어야 하며, 생성된 객체나 AGGREGATE의 불변식을 모두 지켜야 한다.
      • AGGREGATE의 불변식을 모두 지켜야 한다.
      • FACTORY는 일관성 있는 상태에서만 객체를 만들어 낼 수 있어야 한다.
      • ENTITY의 경우 이것은 전체 AGGREGATE를 생성하는 것을 의미
        • 이때 모든 불변식을 충족하고 선택적인 요소도 추가될 것
      • 불변적인 VALUE OBJECT의 경우에는 모든 속성이 올바른 최종 상태로 초기화된다는 것을 의미
    • FACTORY는 생성된 클래스보다는 생성하고자 하는 타입으로 추상화돼야 한다.

FACTORY와 FACTORY의 위치 선정

  • 일반적으로 여러분은 뭔가 감추고 싶은 세부사항이 포함된 것을 생성하고자 FACTORY를 만든 다음 그것을 여러분이 제어할 수 있는 곳에 둔다.
  • 보통 이러한 결정은 AGGREGATE를 중심으로 이뤄진다.
  • 예를 들어, 이미 존재하는 AGGREGATE에 요소를 추가해야 한다면 해당 AGGREGATE의 루트에 FACTORY METHOD를 만들 수도 있다.
  • 이렇게 하면 한 요소가 추가될 때 AGGREGATE의 무결성을 보장하는 책임을 루트가 담당하고, 동시에 모든 외부 클라이언트에게서 AGGREGATE의 내부 구현을 감출 수 있다.
  • 또 다른 예로는 생성된 객체를 소유하지는 않지만 다른 객체를 만들어내는 것과 밀접한 관련이 있는 특정 객체에 FACTORY METHOD를 두는 것
  • 이렇게 하면 한 객체의 데이터나 규칙이 객체를 생성하는 데 매우 크게 영향을 주는 경우 어떤 다른 곳에서 해당 객체를 생성할 때 생산자의 정보를 필요로 하는 것을 줄일 수 있다.
  • 아울러 생산자와 생성된 객체 사이의 특별한 관계를 전해주기도 한다.
  • FACTORY는 해당 FACTORY에서 만들어내는 객체와 매우 강하게 결합돼 있으므로 FACTORY는 자신의 생성물과 가장 밀접한 관계에 있는 객체에 있어야 한다.
  • 대개 독립형(standalone) FACTORY는 전체 AGGREGATE를 생성해서 루트에 대한 참조를 건네주며, 생성된 AGGREGATE의 불변식이 지켜지도록 보장해준다.
  • 특정 AGGREGATE 안의 어떤 객체가 FACTORY를 필요로 하는 데 AGGREGATE 루트가 해당 FACTORY가 있기에 적절한 곳이 아니라면 독립형 FACTORY를 만들면 된다.
  • 하지만 AGGREGATE 내부에 접근하는 것을 제한하는 규칙을 지켜지게 하고 AGGREGATE 외부에서는 FACTORY의 생성물을 일시적으로만 참조하게 해야 한다.

생성자만으로 충분한 경우

  • 직접적으로 생성자를 사용하는 것이 최선의 선택일 때가 있다.
  • FACTORY는 실제로 다형성을 활용하지 않는 간단한 객체를 이해하기 어렵게 만들 수 있다.
  • 타협점을 고려해봤을 때 다음과 같은 상황에서는 공개 생성자 (public constructor) 를 사용하는 편이 좋다.
    • 클래스가 타입인 경우
      • 클래스가 어떤 계층구조의 일부를 구성하지 않으며, 인터페이스를 구현하는 식으로 다형적으로 사용되지 않는 경우
    • 클라이언트가 STRATEGY를 선택하는 한 방법으로서 구현체에 관심이 있는 경우
    • 클라이언트가 객체의 속성을 모두 이용할 수 있어서 클라이언트에게 노출된 생성자 내에서 객체 생성이 중첩되지 않는 경우
    • 생성자가 복잡하지 않은 경우
    • 공개 생성자가 FACTORY와 동일한 규칙을 반드시 준수해야 하는 경우
      • 이때 해당 규칙은 생성된 객체의 모든 불변식을 충족하는 원자적인 연산이어야 한다.
  • 생성자는 극도로 단순해야 한다.
  • AGGREGATE와 같이 복잡한 조립과정을 거쳐 만들어지는 것을 생성하려면 FACTORY가 필요하다.
  • 또한 규모가 작은 FACTORY METHOD를 사용하는 데 드는 비용은 그리 크지 않다.

인터페이스 설계

  • FACTORY의 메서드 서명을 설계할 때는 해당 FACTORY가 독립형이냐 FACTORY METHOD냐에 관계없이 다음의 두 가지 사항을 명심해야 한다.
    • 각 연산은 원자적이어야 한다.
      • 일관성을 지키고자 FACTORY에서 발생하는 실패에 대해 코딩 표준을 도입할 것을 고려해본다.
    • FACTORY는 자신에게 전달된 인자와 결합될 것이다.
      • 입력 매개변수를 선택하는 데 신경 쓰지 않는다면 의존성의 덫이 만들어질 수 있다.
  • 가장 안전한 매개변수는 하위 설계 계층에서 나오는 매개변수
  • 또 다른 매개변수로 적절한 것은 모델 내의 생성물에 밀접하게 관련된 객체
  • 구상 클래스가 아닌 추상적인 타입의 인자를 사용하라.

불변식 로직의 위치

  • FACTORY의 책임은 그것이 만들어내는 객체나 AGGREGATE의 불변식이 충족되도록 보장하는 것
  • FACTORY는 불변식 검사를 생성물에 위임할 수 있으며, 간혹 이렇게 하는 것이 최선일 때도 있다.
  • FACTORY의 존재 이유는 FACTORY의 생성물에 대한 구현과 밀접하게 관련돼 있다.
  • 특정 상황에서는 불변식 로직을 FACTORY에 둬서 생성물에 들어 있는 복잡한 요소를 줄이는 것도 이점이 있다.
  • 대체로 불변식은 모든 연산의 마지막에 적용되지만 이따금 객체에 변형하는 탓에 불변식이 결코 제 역할을 다하지 못할 때도 있다.
  • ENTITY가 생성되고 나면 해당 식별성은 불변성을 띠게 된다.
  • VALUE OBJECT는 완전히 불변적
  • 객체는 해당 객체가 활동하는 생애 동안 결코 적용되지 않을 로직을 수행할 필요는 없다.
  • 그런 경우에는 FACTORY가 불변식을 둘 논리적인 위치가 되며, 생성물은 더욱 단순하게 유지된다.

ENTITY FACTORY와 VALUE OBJECT FACTORY

  • ENTITY FACTORY는 VALUE OBJECT FACTORY 와 두 가지 점에서 다르다.
  • VALUE OBJECT 는 불변적
  • FACTORY의 연산은 생성물에 대해 풍부한 설명을 곁들여야 한다.
  • ENTITY FACTORY는 유효한 AGGREGATE를 만들어 내는 데 필요한 필수 속성만 받아들이는 경향이 있다.
  • 불변식에서 세부사항을 필요로 하지 않는다면 그와 같은 세부사항은 나중에 추가해도 된다.
  • 또 다른 차이점은 ENTITY에는 식별성 할당과 관련된 쟁점이 있다는 것
    • VALUE OBJECT에는 해당하지 않음
  • 프로그램에서 식별자를 할당하는 경우라면 FACTORY는 그와 같은 식별자를 관리하기에 적절한 곳
  • 실질적인 고유 ID는 대개 데이터베이스의 “시퀀스(sequence)”나 기타 인프라스트럭처의 메커니즘으로 생성되지만, FACTORY에서는 무엇을 요청해야 하고 그것을 어디에 둬야 할지 알고 있을 것

저장된 객체의 재구성

  • 재구성에 사용되는 FACTORY는 생성에 사용된 것과 매우 유사하며 주된 차이점으로는 아래의 두 가지가 있다.
    • 재구성에 사용된 ENTITY FACTORY는 새로운 ID를 할당하지 않는다.
      • 저장된 객체를 재구성하는 FACTORY의 입력 매개변수에는 반드시 식별 속성을 포함해야 한다.
    • 객체를 재구성하는 FACTORY는 불변식 위반을 다른 방식으로 처리할 것이다.
      • 새로운 객체를 생성하는 것보다 훨씬 더 재구성을 어렵게 만들 수 잇는 그와 같은 불일치 문제를 해결하기 위한 전략이 어느 정도 마련돼 있어야 한다.
  • 객체 매핑 (object-mapping) 기술은 데이터베이스 재구성을 할 때 이처럼 편리한 서비스의 일부 또는 전부를 제공해줄 수 있다.
  • 요약하면, 인스턴스 생성을 위한 접근 지점을 식별해야 하며, 그러한 접근 지점의 범위는 명시적으로 정의해야 한다.
  • 일반적으로 FACTORY는 모델의 어떤 부분도 표현하지 않지만 해당 모델을 나타내는 객체를 뚜렷하게 드러내는 데 일조하는 도메인 설계의 일부로 볼 수 있다.
  • FACTORY는 객체의 생성과 재구성이라는 생명주기 전이 (transition)를 캡슐화한다.
  • 도메인 설계를 난해하게 할 수 있는 기술적 복잡성을 노출하는 또 하나의 생명주기 전이로는 저장소에 들어갈 때와 저장소에서 나올 때 거치는 전이가 있다.
  • 이러한 전이는 또 다른 도메인 설계 구조물인 REPOSITORY의 책임이다.

REPOSITORY (리파지터리)

  • 객체를 이용해 뭔가를 하려면 해당 객체에 대한 참조를 가지고 있어야 한다.

    • 한 가지 방법은 객체를 생성하는 것
    • 두 번째 방법은 연관관계를 탐색하는 것
  • 데이터베이스 검색은 어디서든 이용할 수 있으며, 곧바로 어떠한 객체에도 접근하게 해준다.

  • 유감스럽게도 개발자들은 보통 그와 같은 설계의 미묘한 사항에 관해 충분히 고민하지 않곤 하는데, 이것은 개발자들이 객체를 저장하고 그것을 다시 가져오고, 결국 객체를 저장소에서 제거하는 요령을 알아내는 메커니즘에만 관심을 두기 때문

  • 이제 기술적 관점에서 보면 저장된 객체를 가져오는 것은 실제로는 생성의 한 부분집합으로 볼 수 있다.

    • 이는 데이터베이스에서 가져온 데이터를 토대로 새로운 객체를 만들어내기 때문
  • 하지만 개념상 이것은 특정 ENTITY의 생명주기 가운데 중간 단계에 불과하다.

  • 이러한 구분을 염두에 두고자 저자는 저장돼 있는 객체로부터 인스턴스를 만들어 내는 것을 재구성(reconstitution)이라고 한다.

  • 도메인 주도 설계의 목표는 기술보다는 도메인에 대한 모델에 집중해 더 나은 소프트웨어를 만들어내는 것

  • 메타데이터 매핑 계층과 같은 인프라스트럭처는 질의 결과를 손쉽게 객체로 변환할 수 있어 굉장히 도움되지만 개발자들은 여전히 도메인이 아닌 기술적 메커니즘에 관해 생각하게 된다.

  • 더 안 좋은 점은 클라이언트 코드에서 직접적으로 데이터베이스를 사용할수록 개발자들은 AGGREGATE 나 캡슐화와 같은 특징을 활용하는 것을 우회하려 하고, 그 대신 필요한 데이터를 직접 획득해서 조작하게 된다는 것

  • 클라이언트는 이미 존재하는 도메인 객체의 참조를 획득하는 실용적인 수단을 필요로 한다.

  • 인프라스트럭처에서 도메인 객체의 참조를 쉽게 획득할 수 있게 해준다면 클라이언트 측을 개발하는 개발자들이 좀 더 탐색 가능한 연관관계를 추가해 모델을 엉망으로 만들어 버릴지도 모른다.

  • 더 중요한 것은 AGGREGATE 내부에 존재하는 모든 객체는 루트로부터 탐색을 토대로 접근하는 것 말고는 접근이 금지돼 있다는 점이다.

  • 대개 영속화된 VALUE OBJECT 는 그것들을 캡슐화하고 AGGREGATE 의 루트 역할을 하는 특정 ENTITY 에서부터 탐색해서 찾을 수 있다.

  • 대부분의 객체는 전역적인 검색으로 접근하지 말아야 한다는 점이 분명해진다.

  • 이러한 점이 설계에 드러아면 바람직할 것

  • 영속 객체는 해당 객체의 속성에 근거해서 검색하는 식으로 전역적으로 접근할 수 있어야 한다.

  • 그러한 접근 방식이 필요한 곳은 탐색으로 도달하기에는 편리하지 않은 AGGREGATE 의 루트다.

  • 일반적으로 루트는 ENTITY 이며, 간혹 복잡한 내부 구조를 지닌 VALUE OBJECT 이거나 열거형 VALUE 이기도 하다.

  • 다른 객체에도 접근할 수 있게 한다면 중요한 구분법이 혼동될 것

  • REPOSITORY는 특정 타입의 모든 객체를 (대개 모방된) 하나의 개념적 집합으로 나타낸다.

  • 더욱 정교한 질의 기능이 있다는 점을 제외하면 REPOSITORY 는 컬렉션처럼 동작한다.

  • 이 같은 정의에는 생명주기의 초기 단계에서 마지막 단계에 이르기까지 AGGREGATE 의 루트에 대한 접근을 제공하는 각종 응집력 있는 책임이 포함된다.

  • REPOSITORY 는 클라이언트에서 요구하는 기준에 근거해 객체를 선택하는 다양한 질의를 구현할 수 있다.

  • 객체를 추가하고 제거하는 메서드를 제공하고, 이 메서드가 실제로 데이터 저장소에 데이터를 삽입하고 데이터 저장소에서 제거하는 연산을 캡슐화하게 하라.

  • 실질적으로 직접 접근해야 하는 AGGREGATE 의 루트에 대해서만 REPOSITORY 를 제공하고, 모든 객체 저장과 접근은 REPOSITORY 에 위임해서 클라이언트가 모델에 집중하게 하라.

  • REPOSITORY 에는 다음과 같은 이점이 있다.

    • REPOSITORY는 영속화된 객체를 획득하고 해당 객체의 생명주기를 관리하기 위한 단순한 모델을 클라이언트에 제시
    • REPOSITORY는 영속화 기술과 다수의 데이터베이스 전략, 또는 심지어 다수의 데이터 소스로부터 애플리케이션과 도메인 설계를 분리
    • REPOSITORY는 객체 접근에 관한 설계 결정을 전해준다.
    • REPOSITORY를 이용하면 테스트에서 사용할 가짜 구현을 손쉽게 대체할 수 있다.
      • 보통 메모리상의 컬렉션을 이용

REPOSITORY에 질의하기

  • 모든 REPOSITORY는 클라이언트가 특정 기준에 부합하는 객체를 요청할 수 있는 메서드를 제공하며, 이러한 인터페이스를 설계하는 방법에는 여러 가지가 있다.
  • 가장 만들기 쉬운 REPOSITORY는 질의에 구체적인 매개변수를 직접 입력하는 것
  • 대부분의 질의가 한 객체나 객체의 컬렉션을 반환하기도 하지만, 질의는 특정 유형의 요약 연산을 반환한다는 개념에도 잘 맞아떨어진다.
  • 프레임워크를 토대로 REPOSITORY 를 일반화하는 한 가지 특별히 적절한 접근법은 SPECIFICATION (명세) 에 기반을 둔 질의를 사용하는 것
  • SPECIFICATION을 이용하면 클라이언트가 질의의 획득 방법에 대해서는 신경 쓰지 않고도 원하는 바를 서술할 수 있다.
  • SPECIFICATION 기반 질의는 우아하고 유연하다.
  • 그러나 유연한 질의를 이용하는 REPOSITORY 설계에서도 특별히 직접 입력된 질의를 추가할 수 있어야 한다.
  • 이러한 부가적인 요소를 제공하는 것이 불가능한 프레임워크는 도메인 설계를 왜곡하거나 개발자들이 해당 프레임워크를 우회해서 뭔가를 하게 만들 것

클라이언트 코드가 REPOSITORY 구현을 무시한다 (개발자는 그렇지 않지만)

  • 영속화 기술을 캡슐화하면 클라이언트가 매우 단순해지고 REPOSITORY 구현에서 완전히 분리된다.
  • 그러나 캡슐화가 종종 그렇듯이 개발자들은 무슨 일이 일어나고 있는지 반드시 알고 있어야 한다.
  • REPOSITORY가 의도하지 않은 방식으로 사용되거나 작동한다면 REPOSITORY의 수행 성능이 극단에 치우칠 수 있다.
  • 개발자들은 캡슐화된 행위를 활용하는 것에 내포된 의미를 알아야 한다.
  • 그러나 그것이 구현상의 상세한 내용까지 잘 알고 있어야 한다는 의미는 아니다.
  • 잘 설계된 컴포넌트라면 특징이 잘 드러날 수도 있다.

REPOSITORY 구현

  • 이상적인 모습은 클라이언트에서 모든 내부 기능을 감춰서 데이터가 객체 데이터베이스나 관계형 데이터베이스에 저장되든, 아니면 단순히 메모리상에 상주하느냐에 관계없이 클라이언트 코드를 동일하게 유지하는 것
  • 저장, 조회, 질의 메커니즘을 캡슐화하는 것은 REPOSITORY 구현의 가장 기본적인 기능
  • REPOSITORY 의 개념은 여러 상황에 적용할 수 있다.
  • 명심해야 할 몇 가지 중요한 사항
    • 타입을 추상화한다.
      • 명심할 점은 현재 이용 중인 데이터베이스 기술에는 그와 같은 다형성이 존재하지 않아서 발생할 수 있는 제약조건에 마주치게 될지도 모른다는 것
    • 클라이언트와의 분리를 활용한다.
      • 이렇게 하면 클라이언트에서 직접 메커니즘을 호출했을 때보다 더 자유롭게 REPOSITORY의 구현을 변경할 수 있다.
      • 이로써 영속화 전략을 자유롭게 교체하면서 질의 기법을 다양하게 하거나 메모리상에 객체를 캐싱해 성능을 최적화할 수도 있다.
    • 트랜잭션 제어를 클라이언트에 둔다.
      • 만약 REPOSITORY에서 간섭하지 않는다면 트랜잭션 관리가 좀 더 단순해질 것
  • 자바와 같은 언어의 타입 체계를 이용하면 이러한 접근법에서 반환되는 객체는 “Object”로 한정되므로 클라이언트에서는 REPOSITORY에 포함된 타입으로 객체를 형변환해야 한다.
  • 하지만 어쨌든 자바에서는 컬렉션을 반환하는 질의로 이렇게 해야 할 것

프레임워크의 활용

  • REPOSITORY와 같은 것을 구현하기 전에 먼저 현재 사용 중인 인프라스트럭처, 특히 모든 아키텍처 프레임워크에 관해 곰곰이 생각해봐야 한다.
  • 일반적으로 현재 사용 중인 프레임워크를 자신의 요구에 억지로 맞추려 해서는 안된다.
  • 도메인 주도 설계의 원칙을 유지할 수 있는 방법을 찾고 프레임워크가 자신의 요구와 맞지 않은 경우에는 명세를 준수한다.
  • 도메인 주도 설계의 개념과 프레임워크의 개념 사이의 유사성을 찾아본다.
  • 이는 우리들이 프레임워크를 사용할 수 밖에 없는 경우를 전제한다.

FACTORY와의 관계

  • FACTORY가 객체 생애의 초기 단계를 다루는 데 반해 REPOSITORY는 중간 단계와 마지막 단계를 관리하는 데 도움된다.
  • REPOSITORY 가 데이터를 근거로 객체를 생성하므로 많은 이들이 REPOSITORY를 FACTORY 로 생각하는데, 사실 기술적 관점에서는 그렇다고 볼 수 있다.
  • 그러나 모델에 중심을 두는 것이 더 유용하며, 앞에서 언급한 것처럼 저장된 객체를 재구성하는 것이 새로운 개념적 객체를 생성하는 것은 아니다.
  • 설계를 이러한 주요한 도메인 주도 관점에서 보면 FACTORY와 REPOSITORY의 책임이 뚜렷이 구분되는데, FACTORY가 새로운 객체를 만들어 내는 데 반해 REPOSITORY는 기존 객체를 찾아낸다.
  • 이러한 두 가지 관점은 REPOSITORY가 맨 처음으로 객체를 생성하는 데도 사용할 수 있는 FACTORY로 객체 생성을 위임해서 일치시킬 수 있다.
  • FACTORY에서 영속화에 대한 모든 책임을 빼내는 것은 이러한 명확한 분리에도 도움된다.
  • 사람들이 FACTORY와 REPOSITORY를 결합하게 만드는 또 한 가지 경우는 “찾아서 없으면 생성하는 (find or create)” 기능을 원할 때
    • 이 기능을 사용하는 것을 자제해야 한다.
    • 이 기능은 기껏해야 조금 더 편리할 뿐
    • 이 기능이 유용해 보일 때는 대부분 ENTITY와 VALUE OBJECT가 확연히 구분되는 경우밖에 없다.
  • 일반적으로 새로운 객체와 이미 존재하는 객체를 구분하는 것은 도메인에서 중요하며, 그리고 그것들을 투명하게 결합하는 프레임워크는 실제로 상황을 엉망으로 만들어버릴 것

관계형 데이터베이스를 위한 객체 설계

  • 객체지향 소프트웨어 시스템에서의 가장 일반적인 비객체 구성요소는 주로 관계형 데이터베이스

  • 데이터베이스는 단순히 객체와 상호작용하는 것만은 아니며, 객체 자체를 구성하는 데이터의 영속적인 형태를 저장

  • 둘 사이의 매핑을 생성하고 관리하는 데 사용되는 상당히 세련된 도구도 있다.

  • 기술적 관점은 별도의 문제로 하더라도 이 같은 불일치는 객체 모델에 상당한 영향을 줄 수 있다.

  • 아래는 세 가지 공통적인 경우를 나열한 것

    • 데이터베이스가 주로 객체를 저장하는 저장소인 경우
    • 데이터베이스가 다른 시스템에서 사용될 용도로 설계된 경우
    • 데이터베이스가 현행 시스템에서 사용될 용도로 설계됐으나 객체 저장 이외의 역할을 하는 경우
  • 문제는 여러 겹으로 교차하는 모델이 너무 복잡하는 것

  • 매핑 도구의 기능에 따라 일부 객체의 애그리게이션(aggregation)이나 컴포지션(composition)이 가능할 것

  • 그러나 매핑은 투명해야 하고, 매핑 도구에 작성된 코드를 검사하거나 항목을 읽기만 해도 쉽게 이해할 수 있어야 한다는 점이 중요

  • 데이터베이스가 하나의 객체 저장소로 보여진다면 매핑 도구의 기능과는 상관없이 데이터 모델과 객체 모델이 서로 갈라지게 해서는 안된다.

    • 객체 매핑을 단순화하는 데 도움된다면 정규화와 같은 정형화된 관계 표준을 절충한다.
  • 객체 시스템의 외부의 프로세스는 이러한 객체 저장소에 접근해서는 안된다.

    • 그러한 프로세스는 객체에 적용된 불변식을 위반할 수 있기 때문
    • 그뿐만 아니라 그러한 프로세스가 접근하게 되면 데이터 모델을 고착화해서 객체를 리팩터링할 때 변경하기가 힘들어진다.
  • 객체지향 도메인은 영속적인 형태로 표현하는 관계형 데이터베이스의 경우에는 단순하게 직접적으로 표현하는 편이 가장 좋다.

  • UBIQUITOUS LANGUAGE 는 객체와 관계형 구성요소를 하나의 모델로 묶는 데 이바지할 수 있다.

  • 객체 세계에서 빠르게 확립되고 있는 리팩터링은 사실 관계형 데이터베이스 설계에는 크게 영향을 주지 못했다.

  • 게다가 중요한 데이터 마이그레이션 문제는 빈번한 변경을 단념하게 만든다.

  • 모델과 데이터베이스 간의 관계를 끊는 것은 현혹적인 경로에 해당한다.

  • 이것은 팀이 데이터베이스와 모델의 대응을 유지하는 데 실패할 때 부지불식간에 받아들여지곤 한다.

  • 하지만 모델과 데이터베이스를 의식적으로 분리한 경우라면 기존의 객체 모델을 따르는 절충안으로 가득 찬 부자연스러운 데이터베이스 스키마가 아닌 깔끔한 데이터베이스 스키마가 만들어질 수 있다.