도메인 주도 설계 철저 입문 3
3장 생에 주기를 갖는 객체 ‘엔티티’
3.1 엔티티란?
도메인 주도 개발에서 말하는 엔티티는 도메인 모델을 구현한 도메인 객체를 의미랍니다.
2장에서 다룬 값 객체와의 차이는 동일성
을 통해 식별이 가능한지 아닌지에 있습니다.
예를 들어,
사람
을 생각해 보겠습니다. 사람
은 각자의 키, 몸무게, 이름 등 다양한 속성을 가집니다.
이때 작은 확률이나마 모든 속성이 똑같은 두 사람
이 존재한다고 할 때, 이 두 사람
은 동일한 사람으로 취급되어야 할까요?
이와 마찬가지로 소프트웨어에도 속성으로 구분되지 않는 객체가 존재합니다.
동일한 속성을 가지는 User
는 다른 객체로 구별되어야 할 것입니다.
이렇듯 속성이 아닌 동일성
을 통해 식별이 가능한 객체를 엔티티
라고 합니다.
3.2 엔티티의 성질
엔티티의 기본적 성질은 다음과 같습니다.
- 가변적이다.
- 속성이 같아도 구분할 수 있다.
- 동일성을 통해 구별된다.
이어서 위 속성들을 하나하나 살펴보겠습니다.
3.2.1 가변적이다.
값 객체와는 달리 엔티티는 가변성을 가집니다. 값 객체와 객체를 교환하는 방식이 아닌 해당 객체의 내부 함수를 이용하여 해당 속성을 수정합니다.
class User {
constructor(
...
) {
...
}
private name: string;
public changeName(name: string) {
if(name === null) throw new Error();
if(name.length < 3>) throw new Error();
this.name = name;
}
}
3.2.2 속성이 같아도 구분할 수 있다.
값 객체는 서로 다른 엔티티를 구별하는데 식별자(identity)를 사용합니다.
class UserId {
constructor(
id: string;
) {
if(id === null || id === undefined) {
throw new Error();
}
this.id = id;
}
public id: string;
}
class User {
...
private readonly id: UserId; // 식별자
private name: string;
...
}
3.2.3 동일성
객체의 속성이 달라지더라도 같은 대상으로 판단하기 위해서는 식별자가 필요합니다. 앞서 소개한 방식의 식별자를 이용하여 모든 속성을 비교하는 것이 아닌 식별자만으로 동일성을 비교할 수 있습니다.
여기서 식별자는 가변일 필요가 없습니다. 따라서 readonly
옵션 등 동일한 기능을 하는 한정자를 통해서
불변 속성으로 생성하는 것이 좋습니다.
3.3 엔티티 판단 기준 - 생애주기와 연속성
값 객체와 엔티티의 판단 기준 중 가장 중요한 것은 생애주기의 연속성
여부입니다.
앞서 계속 나왔던 사용자 객체를 예를 들어 보겠습니다. 사용자 객체는 생성이 되고 사용자, 혹은 관리자의 판단에 의하여 삭제 됩니다. 이와 같이 생애주기를 가지며 연속성을 가지는 객체를 엔티티로 분류하면 됨니다.
반면 생애주기를 갖지 않거나 생애주기를 나타내는 것이 무의미한 개념이라면 일단 값 객체로 다루는 것이 좋다고 합니다.
3.4 값 객체도 되고 엔티티도 될 수 있는 모델
객체는 시스템에 따라 값 객체가 될 수도 있고 엔티티가 될 수도 있습니다.
예를들어 문구점에서의 연팔은 상품이며 값 객체로 나타내기에 적합합니다. 반면, 연필 공장에서는 이를 엔티티로 식별하는 것이 적합할 것입니다.
이처럼 같은 대상이라도 어떤 환경에 있느냐에 따라 모델링 방법을 달리해야 합니다.
3.5 도메인 객체를 정의할 때의 장점
엔티티와 값ㄱ 객체 같은 도메인 객체를 사용함으로서 얻는 장점은 크게 2가지입니다.
- 자기 서술적인 코드가 된다.
- 도메인에 변경사항이 있을 시 코드에 반영하기 쉽다.
3.5.1 자기 서술적인 코드가 된다.
개발자로 일을 하다보면 전임자로부터 인수인계를 받는 경우나 프로젝트 중간에 참여한 경우 등 다양한 이유에서 내가 전혀 모르는 코드를 다뤄야 할 때가 많습니다. 이 경우 대부분 기능명세서 같은 문서를 통해 파악을 합니다. 하지만 기능 명세서는 대략적인 사항을 파악하기엔 적합하지만 디테일을 담기엔 한계가 있습니다.
그럴때 개발자는 코드에 의존하여 해석하는 방법 밖에 없습니다.
그런데 만약 이런 상황에서 코드가 다음과 같다면 어떨까요?
class User {
public name: string;
}
위의 코드를 아무리 봐도 어떤 인사이트를 얻기 힘듭니다.
하지만 다음과 같은 코드는 어떨까요?
class UserName {
constructor(name: string) {
if(name === null) throw new Error('이름이 필요합니다');
if(name.length < 3) throw new Error('이름은 적어도 3글자 이상이여야 합니다.');
this.name = name;
}
...
}
위의 고드를 보시면 사용자명은 3글자 이상이여야 한다는 것을 명확하게 알 수 있습니다.
3.5.2 도메인에 일어난 변경을 코드에 반영하기 쉽다.
위에 예시에 이어서 사용자명이 3글자가 아닌 6글자 이상이라고 규칙이 바뀌었다고 가정해 보겠습니다.
이 경우 도메인 객체가 아닌 단순 코드라면 변화된 규칙을 반영하기가 힘들 것입니다. 하지만 도메인 객체를 이용하는 경우
// 3글자 -> 6글자
if (name.length < 6) throw new Error("이름은 적어도 6글자 이상이여야 합니다.");
이처럼 단순한 변화로 도메인 전반에 영향을 미칠 수 있습니다.
본 글은
도메인 주도 설계 시리즈로 작성 됩니다.
도메인 주도 설계 1
도메인 주도 설계 2
도메인 주도 설계 3
도메인 주도 설계 4
댓글남기기