3.6 도메인 모델의 기반 구현

3.6 도메인 모델의 기반 구현

3.6 도메인 모델의 기반 구현

도메인 주도 설계의 핵심 구성요소들을 구현해보도록 하겠습니다. 먼저 공유 커널(Shared Kernel)에 해당하는 기반 클래스들을 살펴보겠습니다.

3.6.1 엔티티와 값 객체의 기반 클래스

도메인 모델에서 가장 기본이 되는 개념은 엔티티(Entity)와 값 객체(Value Object)입니다. 이 두 개념은 각각 다른 식별성과 동등성 비교 방식을 가지므로, 이를 명확하게 구분하여 구현해야 합니다.

Entity 인터페이스

모든 엔티티의 기본이 되는 Entity 인터페이스입니다:

public interface Entity<ID> {
    ID getId();
}

이 인터페이스는 다음과 같은 특징을 가집니다:

  1. 제네릭 타입 ID를 통해 다양한 식별자 타입을 지원합니다.
  2. getId() 메서드를 통해 엔티티의 식별자에 접근할 수 있습니다.
  3. 최소한의 기능만을 정의하여 유연성을 제공합니다.

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());
    }
}

이 구현의 주요 특징은 다음과 같습니다:

  1. 엔티티의 동등성을 식별자를 기준으로 정의합니다.
  2. null 체크와 타입 체크를 포함한 안전한 equals 구현을 제공합니다.
  3. equals와 일관된 hashCode 구현을 제공합니다.

ValueObject 인터페이스

값 객체의 기본이 되는 인터페이스입니다:

public interface ValueObject extends Serializable {
}

이 인터페이스는 다음과 같은 특징을 가집니다:

  1. Serializable을 상속하여 직렬화 가능성을 보장합니다.
  2. 마커 인터페이스로서 값 객체임을 명시적으로 표현합니다.

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());
    }
}

이 구현의 주요 특징은 다음과 같습니다:

  1. 값 객체의 동등성을 모든 속성값의 동등성으로 정의합니다.
  2. getAtomicValues() 메서드를 통해 비교할 속성값들을 정의할 수 있습니다.
  3. 배열 비교를 통해 여러 속성값들의 동등성을 효율적으로 검사합니다.

3.6.2 애그리거트 루트 구현

애그리거트는 하나의 단위로 취급되어야 하는 연관된 객체들의 집합입니다. 애그리거트 루트는 이 집합의 일관성을 책임지는 엔티티입니다.

AggregateRoot 인터페이스

public interface AggregateRoot<ID> extends Entity<ID> {
    void registerDomainEvent(DomainEvent event);
    Collection<DomainEvent> getDomainEvents();
    void clearDomainEvents();
}

이 인터페이스의 주요 특징은 다음과 같습니다:

  1. Entity를 상속하여 식별성을 가집니다.
  2. 도메인 이벤트를 등록하고 관리하는 기능을 정의합니다.
  3. 이벤트의 수명주기를 관리할 수 있는 메서드를 제공합니다.

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();
    }
}

이 구현의 주요 특징은 다음과 같습니다:

  1. 도메인 이벤트를 안전하게 관리하는 컬렉션을 제공합니다.
  2. 이벤트 컬렉션의 불변성을 보장합니다.
  3. 이벤트의 등록과 조회, 초기화를 일관되게 처리합니다.

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);
    }
}

이 구현의 주요 특징은 다음과 같습니다:

  1. 단일 이벤트와 이벤트 컬렉션의 발행을 모두 지원합니다.
  2. default 메서드를 통해 컬렉션 처리의 기본 구현을 제공합니다.

정리

이러한 기반 클래스들의 구현을 통해 다음과 같은 이점을 얻을 수 있습니다:

  1. 도메인 모델의 핵심 개념들을 명확하게 표현
  2. 반복적인 구현의 중복을 제거
  3. 일관된 동작 방식 보장
  4. 도메인 규칙의 캡슐화 지원
  5. 이벤트 기반 아키텍처의 기반 제공
Last updated on