DDD와 MSA 기초(2)

DDD와 MSA에 대해 여러 시리즈로 정리합니다. 이번 편에서는요,

- 이벤트
    - 비동기 이벤트 처리
- CQRS

에 대해 다룹니다.

참고: 도서 DDD START! 도메인 주도 설계 구현과 핵심 개념 익히기


이벤트

이벤트(event)는 과거에 벌어진 어떤 것을 의미합니다. 이벤트가 발생한다는 것은 상태가 변경됐다는 것을 의미합니다. 이벤트는 발생하는 것에서 끝나지 않고 그 이벤트에 반응하여 원하는 동작을 수행하는 기능을 구현합니다.

image

예시 - 이벤트

public class ShippingInfoChangedEvent {
    
    private String orderNumber;
    private long timestamp;
    private ShippingInfo newShippingInfo;
}

예시 - 이벤트 핸들러

public class ShippingInfoChangedHandler implements EventHandler<ShippingInfoChangedEvent> {
    
    @Override
    public void handle(ShippingInfoChangedEvent event) {
        shippingInfoSynchronized.sync(event.getOrderNumber(), event.getNewShippingInfo());
    }
  ...
}

image


이벤트 처리 흐름

image

참고: 응용 서비스와 동일한 트랜잭션 범위에서 핸들러의 handle()이 실행되는 것을 알 수 있는데 도메인의 상태 변경과 이벤트 핸들러는 같은 트랜잭션 범위에서 실행되는 것을 알 수 있습니다.


비동기 이벤트 처리

외부 시스템과의 연동을 동기로 처리할 때 발생하는 성능과 트랜잭션 범위 문제를 해결하는 방법 중 하나가 이벤트를 비동기로 처리하는 것입니다.

“A하면 이어서 B하라 => A하면 최대 언제까지 B하라”

A 이벤트가 발생하면 별도 스레드로 B를 수행하는 핸들러를 실행하는 방식으로 요구사항을 구현할 수 있습니다.

비동기 이벤트 처리 - 1) 로컬 핸들러의 비동기 실행

동기와 비동기로 실행할 이벤트를 처리할 때의 차이점은 동기는 실행할 이벤트 핸들러를 바로 실행하지만, 비동기로 실행할 이벤트 핸들러는 executor.submit()을 이용해서 스레드 풀에 핸들러 실행 작업을 등록한다는 점입니다. 별도 스레드로 이벤트 핸들러를 사용한다는 것은 raise()메서드와 관련된 트랜잭션 범위에 이벤트 핸들러 실행이 묶이지 않는다는 것을 의미합니다. 별도 스레드를 이용해서 이벤트 핸들러를 실행하면 이벤트 발생 코드와 같은 트랜잭션 범위에 묶을 수 없기 떄문에 한 트랜잭션으로 실행해야 하는 이벤트 핸들러는 비동기로 처리해서는 안됩니다.

비동기 이벤트 처리 - 2) 메시징 시스템을 이용한 비동기 구현

메시징 시스템은 글로벌 트랜잭션을 지원하면서 클러스터와 고가용성도 지원하기 때문에 안정적으로 메시지를 전달할 수 있는 장점이 있습니다. (Kafka는 글로벌 트랜잭션을 지원하지는 않지만 성능이 높음) 이벤트가 발생하면 이벤트 디스패처가 이벤트를 -> 메시지 큐에 보내고 메시지 큐는 이벤트를 -> 메시지 리스너에 전달합니다. 메시지 리스너는 알맞은 이벤트 핸들러를 이용해서 이벤트를 처리합니다. 이때 이벤트를 메시지 큐에 저장하는 과정과 메시지 큐에서 이벤트를 읽어와 처리하는 과정은 별도 스레드나 프로세스로 처리됩니다.

image

비동기 이벤트 처리 - 3) 이벤트 저장소를 이용한 비동기 처리(포워더, API)

이벤트를 일단 DB에 저장한 뒤에 포워더와 같은 별도 프로그램을 이용해서 이벤트 핸들러에 저장할 수도 있습니다. 포워더는 주기적으로 이벤트 저장소에서 이벤트를 읽어와 이벤트 핸들러를 실행합니다. 포워더는 별도 스레드를 이용하기 때문에 이벤트 발행과 처리가 비동기로 처리됩니다. 도메인의 상태와 이벤트 저장소로 동일한 DB를 사용하며 도메인의 상태 변화와 이벤트 저장이 로컬 트랜잭션으로 처리됩니다. 이벤트를 물리적 저장소에 보관하기 때문에 핸들러가 이벤트 처리에 실패할 때 포워더가 다시 읽어와서 실행할 수 있습니다.
이벤트를 외부에 제공하는 API 방식도 있는데 외부 핸들러가 API 서버를 통해 이벤트 목록을 가져오는 방식입니다. 이벤트 목록을 요구하는 외부 핸들러가 자신이 이벤트를 어디까지 처리했는지 기억해야 합니다.



CQRS (Command Query Responsibility Segregation)

여러 애그리거트에서 데이터를 가져와 출력하는 기능을 구현할 때에는 고려할 것들이 많아서 구현이 복잡해질 수 있는데 이러한 복잡도를 낮추는 방법이 상태 변경을 위한 모델조회를 위한 모델을 분리하는 것입니다.

CQRS는 복잡한 도메인에 적합하며 CQRS를 통해 각 모델에 맞는 구현 기술을 선택할 수 있습니다.

명령 모델과 조회 모델을 서로 다른 기술을 이용해서 구현 명령 모델과 조회 모델이 서로 다른 DB를 사용(두 데이터 저장소 간 동기화는 이벤트 활용)