비즈니스 로직이 여러 서비스에 흩어져 있는 마이크로서비스 아키텍처는 복잡한 비즈니스 로직을 개발하기 까다롭다. 문제는 다음 두가지가 있다.
이 문제는 DDD 애그리거트 패턴으로 해결할 수 있다.
: 절차적인 코드 작성, 객체 지향 설계를 하지 않는 것
: 객체 지향 설계, 비즈니스 로직을 상태와 동작을 가진 클래스로 구성된 객체 모델로 구성
@Entity
전통적인 객체 지향 설계에 기반하면 경계가 불분명하게 된다..
애그리거트는 한 단위로 취급 가능한 경계 내부의 도메인 객체들이다. 하나의 루트 엔터티와 하나 이상의 기타 엔터티 + 밸류 객체로 구성된다.
애그리거트는 작을 수록 좋지만 애그리거트 자체가 곧 트랜잭션 범위라 크게 잡아야할 수도 있다.
뭐 그래도 가급적 잘게 나누는 것이 최선이란다!
애그리거트는 상태가 전이될 때마다 이에 관련된 컨슈머를 위해 이벤트를 발행한다.
intetface DomainEvent {}
interface OrderDomainEvent extends DomainEvent{}
class OrderCreatedEvent implements OrderDomainEvent {}
interace DomainEventEnvelope<T extends DomainEvent> {
String getAggregateId();
Message getMessage();
String getAggregateType();
String getEventId();
T getEvent();
}
(주문 이벤트 예시) 이벤트 컨슈머가 이벤트를 받아 처리하려면 주문 내역이 필요하다. 이때 필요한 정보를 OrderService
에서 직접 가져와도 되지만 이벤트 컨슈머가 서비스를 쿼리하는 것은 오버헤드를 유발한다.
따라서 컨슈머에 필요한 정보를 이벤트가 갖고 다니는 이벤트 강화 기법을 적용한다.
class OrderCreatedEvent implements OrderEvent {
//...
private DeliveryInformation deliveryInformation; //컨슈머가 필요로 하는 데이터
}
단점은 컨슈머 요건이 바뀌면 이벤트 클래스도 함께 바꿔야한다는 점 .. 흠
도메인 이벤트는 애그리거트가 발행한다. 애그리거트는 자신의 상태가 변경되는 시점과 그 결과 어떤 이벤트를 발행할지 알고 있다.
애그리거트는 상태 전이 시 이벤트를 생성하고, 이렇게 생성한 이벤트를 두 가지 방법으로 서비스에 반환한다.
//애그리거트
public class Ticket {
public List<TicketDomainEvent> accept(LocalDateTime readyBy) {
...
this.acceptTime = LocalDateTime.now(); // Ticket 업데이트
this.readyBy = readyBy;
return singletonList(new TicketAcceptedEvent(readyBy)); // 이벤트 반환
}
}
//서비스
@Transactional
public class KitchenService {
@Autowired
private TicketRepository ticketRepository;
@Autowired
private TicketDomainEventPublisher domainEventPublisher;
public void accept(long ticketId, LocalDateTime readyBy) {
Ticket ticket = ticketRepository.findById(ticketId)
.orElseThrow(() -> new TicketNotFoundException(ticketId));
List<TicketDomainEvent> events = ticket.accept(readyBy); // ticket.accept 호출, 업데이트
domainEventPublisher.publish(ticket, events); // 도메인 이벤트 발행
}
}
애그리거트를 업데이트하는 트랜잭션의 일부로 이벤트를 발행하기 위해 트랜잭셔널 메시징을 사용해야 한다.
-> 아웃박스 패턴 사용
메뉴 변경 이벤트 들어오면 업데이트 후 도메인 이벤트 목록 반환하도록 한다..