3.6 도메인 모델의 기반 구현
3.6 도메인 모델의 기반 구현
도메인 주도 설계의 핵심 구성요소들을 구현해보도록 하겠습니다. 먼저 공유 커널(Shared Kernel)에 해당하는 기반 클래스들을 살펴보겠습니다.
3.6.1 엔티티와 값 객체의 기반 클래스
도메인 모델에서 가장 기본이 되는 개념은 엔티티(Entity)와 값 객체(Value Object)입니다. 이 두 개념은 각각 다른 식별성과 동등성 비교 방식을 가지므로, 이를 명확하게 구분하여 구현해야 합니다.
Entity 인터페이스
모든 엔티티의 기본이 되는 Entity 인터페이스입니다:
public interface Entity<ID> {
ID getId();
}이 인터페이스는 다음과 같은 특징을 가집니다:
- 제네릭 타입 ID를 통해 다양한 식별자 타입을 지원합니다.
- getId() 메서드를 통해 엔티티의 식별자에 접근할 수 있습니다.
- 최소한의 기능만을 정의하여 유연성을 제공합니다.
AbstractEntity 구현
Entity 인터페이스의 기본 구현을 제공하는 추상 클래스입니다:
public abstract class AbstractEntity<ID> implements Entity<ID> {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
AbstractEntity other = (AbstractEntity) obj;
return Objects.equals(getId(), other.getId());
}
@Override
public int hashCode() {
return Objects.hash(getId());
}
}이 구현의 주요 특징은 다음과 같습니다:
- 엔티티의 동등성을 식별자를 기준으로 정의합니다.
- null 체크와 타입 체크를 포함한 안전한 equals 구현을 제공합니다.
- equals와 일관된 hashCode 구현을 제공합니다.
ValueObject 인터페이스
값 객체의 기본이 되는 인터페이스입니다:
public interface ValueObject extends Serializable {
}이 인터페이스는 다음과 같은 특징을 가집니다:
- Serializable을 상속하여 직렬화 가능성을 보장합니다.
- 마커 인터페이스로서 값 객체임을 명시적으로 표현합니다.
AbstractValueObject 구현
ValueObject 인터페이스의 기본 구현을 제공하는 추상 클래스입니다:
public abstract class AbstractValueObject implements ValueObject {
protected abstract Object[] getAtomicValues();
@Override
public boolean equals(Object o) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
AbstractValueObject other = (AbstractValueObject) obj;
return Arrays.equals(getAtomicValues(), other.getAtomicValues());
}
@Override
public int hashCode() {
return Objects.hash(getAtomicValues());
}
}이 구현의 주요 특징은 다음과 같습니다:
- 값 객체의 동등성을 모든 속성값의 동등성으로 정의합니다.
- getAtomicValues() 메서드를 통해 비교할 속성값들을 정의할 수 있습니다.
- 배열 비교를 통해 여러 속성값들의 동등성을 효율적으로 검사합니다.
3.6.2 애그리거트 루트 구현
애그리거트는 하나의 단위로 취급되어야 하는 연관된 객체들의 집합입니다. 애그리거트 루트는 이 집합의 일관성을 책임지는 엔티티입니다.
AggregateRoot 인터페이스
public interface AggregateRoot<ID> extends Entity<ID> {
void registerDomainEvent(DomainEvent event);
Collection<DomainEvent> getDomainEvents();
void clearDomainEvents();
}이 인터페이스의 주요 특징은 다음과 같습니다:
- Entity를 상속하여 식별성을 가집니다.
- 도메인 이벤트를 등록하고 관리하는 기능을 정의합니다.
- 이벤트의 수명주기를 관리할 수 있는 메서드를 제공합니다.
AbstractAggregateRoot 구현
public abstract class AbstractAggregateRoot<ID>
extends AbstractEntity<ID> implements AggregateRoot<ID> {
private final List<DomainEvent> domainEvents = new ArrayList<>();
@Override
public void registerDomainEvent(DomainEvent domainEvent) {
domainEvents.add(domainEvent);
}
@Override
public Collection<DomainEvent> getDomainEvents() {
return Collections.unmodifiableCollection(domainEvents);
}
@Override
public void clearDomainEvents() {
domainEvents.clear();
}
}이 구현의 주요 특징은 다음과 같습니다:
- 도메인 이벤트를 안전하게 관리하는 컬렉션을 제공합니다.
- 이벤트 컬렉션의 불변성을 보장합니다.
- 이벤트의 등록과 조회, 초기화를 일관되게 처리합니다.
3.6.3 도메인 이벤트 구현
도메인 이벤트는 도메인에서 발생한 의미 있는 변화를 표현합니다.
DomainEvent 인터페이스
public interface DomainEvent {
}TimedDomainEvent 인터페이스
시간 정보를 포함하는 도메인 이벤트를 위한 확장 인터페이스입니다:
public interface TimedDomainEvent extends DomainEvent {
LocalDateTime occurredAt();
}DomainEventPublisher 인터페이스
도메인 이벤트의 발행을 담당하는 인터페이스입니다:
public interface DomainEventPublisher {
void publish(DomainEvent event);
default void publishAll(Collection<DomainEvent> events) {
events.forEach(this::publish);
}
}이 구현의 주요 특징은 다음과 같습니다:
- 단일 이벤트와 이벤트 컬렉션의 발행을 모두 지원합니다.
- default 메서드를 통해 컬렉션 처리의 기본 구현을 제공합니다.
정리
이러한 기반 클래스들의 구현을 통해 다음과 같은 이점을 얻을 수 있습니다:
- 도메인 모델의 핵심 개념들을 명확하게 표현
- 반복적인 구현의 중복을 제거
- 일관된 동작 방식 보장
- 도메인 규칙의 캡슐화 지원
- 이벤트 기반 아키텍처의 기반 제공
Last updated on