Enjoy My Posts

DDD - 엔터티

Posted on By Geunwon Lim

도메인 개념의 개별성에 신경을 쓸 때, 그러니까 한 개념을 시스템 내의 나머지 모든 객체와 반드시 구분해야 하는 제약 조건이 있을 때 이를 엔터티로 설계한다.

고유 식별자와 변화 가능성이라는 특징이 엔터티와 값 객체 사이의 차이점이다.

대부분 개념은 값으로 모델링 해야 한다.

일반적 CRUD 만 필요한 CRUD 기반의 시스템에는 그루비 앤 그레일스, 루비 온 레일스 등과 같은 언어가 합리적일 수 있다. 반면 CRUD를 시스템에 잘못 적용하면 후회하게 된다.

복잡성이 증가하면 잘못된 도구 선택에 따른 제약이 생긴다. CRUD 시스템에선 데이터의 포착만으로 정교한 비즈니스 시스템을 생성할 수 없다.

객체를 특성이 아니라 식별자에 따라 구분한ㅋ다면 모델을 정의할 때 이를 우선적으로 다뤄라. 클래스의 정의를 단순하게 유지하면서 수명 주기의 지속성과 식별자에 집중하자.

고유 식별자

엔터티 설계 초기엔 고유 식별자의 중심을 이루는 우선적인 특성과 행동을 비롯해 이를 쿼리하는 데 도움을 주는 요소에 의도적으로 집중하고, 우선적 사항을 마무리할 때까진 다른 특성이나 행동을 의도적으로 무시한다.

엔터티를 정의할 때 특성이나 행동에 집중하기보단 엔터티 객체를 식별하거나 매칭해서 찾을 때 사용되는 엔터티 객체의 정의를 가장 기본적인 특성까지 파고들어야 한다. 개념적으로 필수적인 행동과 그 행동에서 필요로 하는 특성만 추가하자.

고유 식별자를 탐색이나 매칭에 사용할 수 있는지 여부는 식별자가 얼마나 사람이 읽기 쉽도록 돼 있는지에 달려 있다.

값 객체는 고유 식별자의 홀더 역할을 할 수 있다. 값 객체는 불변하기 때문에 식별자의 ㅇㅏㄴ정성이 확보되고 식별자의 유형에 따른 모든 행동은 중앙집중화 된다. 식별자 행동의 중심점이 생기면 단순함의 종더와는 상관없이 모델의 다른 부분이나 클라이언트로 노하우가 새나가는 것을 막아준다.

일반적 식별자 생성 전략

  1. 사용자가 하나 이상의 초기 고유값을 입력, 애플리케이션은 이것이 고유한지 확인
  2. 애플리케이션이 고유성이 보장되는 알고리즘을 사용해 식별자 생성
  3. 데이터베이스와 같은 영속성 저장소가 고유 식별자 생성
  4. 다른 바운디드 컨텍스트에서 먼저 고유 식별자 결정

1. 사용자가 식별자 제공

간단해보이지만 약간 문제가 있음.

  1. 양질의 식별자 생성을 사용자에게 의지 - 해결을 위해 설계 토론부터 시작. 실패를 방지하기 위한 접근법을 고려 후 사용자가 고유 식별자를 정의토록 해야 함. 식별자 승인이 적합할 수 있음. 사용자 입력은 매칭에 사용해도 좋지만, 이를 고유 식별자로 사용해선 안됨.

2. 애플리케이션이 식별자를 생성

UUID, GUID 등. 드물게 메모리 부담 때문에 비실용적 렌더링이 발생하기도 함. 이럴 땐 영속성 메커니즘을 통해 생성한 8바이트의 긴 식별자가 문제를 개선할 수 있음.

UUID는 일반적으로 사용자에게 감춰지고, 대신 사람이 읽을 수 있는 참조 기법을 사용한다. 짧게 줄인 식별자는 애그리게잇 경계 내에서 엔터티의 로컬 식별자로만 쓰일 때 더 신뢰할만하다. 로컬 식별자는 애그리게잇 안의 엔티티가 같은 애그리게잇에 속한 엔터티 중 유일하게 구분됨을 의미한다. 애그리게잇 루트로서 쓰이는 엔터티는 전역 고유 식별자를 필요로 한다.

이런 종류의 식별자를 스트링으로 유지관리하는 일이 좋은 선택은 아닐 것. 식별자 객체가 더 맞다.

필자는 애그리게잇 루트 식별자를 생성할 땐 리파지토리를 사용한다고 함.

3. 영속성 메커니즘이 식별자 생성

이 방식의 이점은 데이터 베이스로 시퀀스나 증가 값을 호출한 결과가 언제나 고유하다는 것이다.

성능 측면에서 단점이 될 수 있다. 값을 얻기 위해 디비까지 가야한다는 점이 애플리케이션 안에서 식별자를 생성할 때에 비해 많은 시간을 소모할 수 있다. 이는 디비 부하와 애플리케이션의 요청에 따라 달라진다. 이 문제를 피하는 방법으론 리파지토리 내부와 같이 애플리케이션의 안쪽에 시퀀스/증가 값을 캐싱하는 방법이 있다. 이 방법이 잘 작동할수도 있찌만 보통 서버 노드가 재시작되면 아직 사용되지 않은 값은 유실된다.

모델이 늦은 식별자 생성만으로도 충분하다면 미리 할당해 캐싱하는 방법은 문제가 되지 않는다.

이 부분은 어려워서 이해 못한 부분이 많음. 나중에 다시읽어보기

순서가 중요할 수 있다

때론 식별자 엔터티의 생성과 할당이 일어나는 시점이 중요하다. 빠른 식별자 생성과 할당은 엔터티가 저장되기 전에 일어난다. 늦은 식별자 생성과 할당은 엔터티가 저장될 때 일어난다.

4. 또 하나의 바운디드 컨텍스트가 식별자를 할당

또 다른 컨텍스트가 식별자를 할당할땐 각 식별자의 검색과 매칭과 할당을 위한 통합이 필요.

여기선 동기화에 영향을 받음. 외부의 참조된 객체의 상태가 변하면서 로컬 엔터티에 영향을 미치면? 관련 객체의 상태가 변했는지 어떻게 알 수 있을까? 이 문제는 이벤트 주도 아키텍처를 도메인 이벤트와 함께 사용해 해결할 수 있다.

이 방법은 식별자 생성 전략 중 가장 복잡. 보수적으로 사용하라.

식별자 생성의 시점이 문제가 될 때

클라이언트가 외부의 도메인 이벤트를 구독하는 시나리오에서, 클라이언트가 새로운 엔티티를 리포지토리에 추가할 기회를 잡기 전에 도메인 이벤트를 수신한다. 따라서 도메인 이벤트가 올바르게 초기화되기 위해선 식별자 생성을 빠르게 완료해야 한다.

그리고 둘 이상의 새로운 엔티티를 Set에 추가해야하는데, 식별자가 아직 할당되지 않았다면 해당 식별자와 다른 새로운 엔티티의 식별자가 같아질 수 있다. 이런 상황을 타개하기 위해선 식별자를 초기에 가져와 할당하도록 설계하거나, eqauls 메소드를 리팩토링해 도메인 식별자가 아닌 다른 속성과 비교토록 해야한다.

저자는 Set에 추가하는 상황에 직면하면 값으로 일치성을 확인하는 것보단 빠르게 가져와 할당하는 방법을 선호한다고 함.

대리 식별자

도메인 식별자가 데이터베이스의 기본 키 역할을 수행해야 할 필요는 없다.

식별자 안정성

대부분 식별자는 수정하지 못하도록 보호되고, 할당된 엔터티의 수명 주기에 걸쳐 안정적으로 유지돼야 한다. 식별자 수정을 방지하기 위해 식별자 세터를 클라이언트로부터 숨길 수 있고, 만약 세터가 이미 존재한다면 식별자의 상태 변화로부터 엔터티 자체를 보호하기 위해 세터 내에 가드를 만들 수도 있다.