본문으로 건너뛰기

분산 이벤트 기반 CQRS 연동 아키텍처 패턴

1. 개요

본 문서는 마이크로서비스 아키텍처에서 한 도메인에서 발생한 이벤트가 분산 이벤트 시스템을 통해 다른 도메인으로 전파되고, 수신 측 도메인에서 내부 CQRS (Command Query Responsibility Segregation) 패턴을 통해 관련 데이터를 처리하는 일반적인 아키텍처 패턴을 설명합니다. 이 패턴은 도메인 간의 결합도를 낮추고, 각 시스템의 독립적인 확장성과 유지보수성을 높이는 데 목적이 있습니다.

이벤트 시스템의 기반 아키텍처는 @event-system.md 문서를 참조합니다.

2. 코어 아키텍처 컴포넌트

이 패턴에서 사용되는 주요 컴포넌트와 역할은 다음과 같습니다.

  • 이벤트 발행 도메인 (Publishing Domain Service):
    • 도메인 로직: 특정 비즈니스 로직 수행 후 도메인 이벤트를 발생시킵니다.
    • 로컬 EventEmitter: 발생된 도메인 이벤트를 내부적으로 발행합니다.
    • EventBridgeService: 로컬 이벤트를 감지하여 중앙 메시지 브로커(예: GCP Pub/Sub)로 전달하는 역할을 합니다. (상세 내용은 @event-system.md 참조)
  • 중앙 메시지 브로커 (Central Message Broker):
    • GCP Pub/Sub (또는 유사 시스템): 이벤트의 안정적인 전달을 보장하는 중앙 집중형 메시지 토픽입니다. 모든 도메인 이벤트는 단일 토픽 (events)으로 게시될 수 있습니다.
  • 이벤트 수신 도메인 (Subscribing Domain Service):
    • WebhooksController: 메시지 브로커로부터 Push 방식으로 이벤트를 수신하는 엔드포인트입니다 (/webhooks/events).
    • EventsService: WebhooksController로부터 Pub/Sub 메시지를 받아 처리합니다. 인증, 중복 메시지 처리(eventId 및 Redis 활용) 등을 수행합니다. (상세 내용은 @event-system.md 참조)
    • 로컬 EventEmitter: EventsService가 수신한 외부 이벤트를 내부적으로 관심 있는 핸들러가 구독할 수 있도록 특정 타입의 로컬 이벤트로 다시 발행합니다.
    • 이벤트 핸들러 (Event Handler): 특정 로컬 이벤트를 구독하여, 관련된 비즈니스 로직을 트리거합니다. 이 핸들러는 주로 Command를 발행하는 역할을 합니다.
    • CommandBus: 발행된 Command를 적절한 Command Handler로 라우팅합니다.
    • Command Handler: 특정 Command를 수신하여 실제 데이터 변경 로직을 수행합니다. 내부적으로 Repository를 사용하여 도메인 모델의 상태를 변경합니다.
    • Repository: 데이터 영속성을 관리하며, 도메인 모델과 데이터베이스 간의 상호작용을 담당합니다.
    • 데이터베이스 (Database): 도메인의 데이터를 저장합니다.

3. 일반적인 흐름 (구조도)

다음은 이벤트 발행 도메인에서 이벤트가 발생하여 수신 도메인에서 CQRS 패턴을 통해 처리되기까지의 일반적인 구조입니다.

4. 상호작용 시퀀스 (일반)

5. 구현 예시: 사용자 삭제 시나리오

이 아키텍처 패턴을 적용한 구체적인 예시로, 사용자(User) 도메인에서 사용자가 영구적으로 삭제되었을 때 발생하는 UserHardDeletedEventQuestionnaire 도메인이 수신하여, 해당 사용자와 관련된 모든 설문 데이터를 처리하는 과정을 살펴보겠습니다.

5.1. 전체 흐름도 (시퀀스 다이어그램 - 사용자 삭제 예시)

5.2. 이벤트 처리 흐름 (구조도 - 사용자 삭제 예시)

event-system.md 아키텍처를 기반으로 UserHardDeletedEvent 처리 및 Questionnaire 도메인 데이터 삭제 과정을 도식화하면 다음과 같습니다.

5.3. Questionnaire 도메인 내부 컴포넌트 관계 (사용자 삭제 예시)

5.4. 주요 단계 설명 (사용자 삭제 예시)

  1. User 도메인 (feature-user): 사용자 영구 삭제 비즈니스 로직이 실행된 후, UserHardDeletedEvent를 로컬 EventEmitter'event' 채널로 발행합니다.
  2. EventBridgeService: 'event' 채널에서 이벤트를 감지하고, event-system.md에 정의된 표준 형식(실제 이벤트 타입은 페이로드 내 type 필드에 명시)으로 변환하여 GCP Pub/Sub의 중앙 'events' 토픽으로 게시합니다.
  3. GCP Pub/Sub: 'events' 토픽에 연결된 Push 구독을 통해 API Gateway의 지정된 엔드포인트 (WebhooksController)로 메시지를 전달합니다.
  4. WebhooksController (dta-wide-api): /webhooks/events 엔드포인트에서 Pub/Sub 메시지를 수신하고 EventsService에 처리를 위임합니다.
  5. EventsService (dta-wide-api): 메시지 인증, eventId를 사용한 중복 처리(Redis 활용)를 수행합니다.
  6. 로컬 EventEmitter (API Gateway): EventsService는 수신한 메시지의 type 필드 (예: 'user.hard_deleted')를 사용하여 해당 타입의 로컬 이벤트를 발행합니다. 이때, 원본 StandardEvent를 전달합니다.
  7. QuestionnaireCrossEventReceiver (feature-questionnaire): @OnEvent('user.hard_deleted') 데코레이터를 통해 로컬 이벤트를 구독하고, 이벤트 페이로드(StandardEvent)를 수신합니다.
  8. handleUserQuestionnaireDataDeletion (feature-questionnaire): QuestionnaireCrossEventReceiver는 수신한 userId를 사용하여 데이터 삭제 처리를 위한 메소드를 호출합니다. (현재 TODO 상태)
  9. 향후 CQRS 패턴 도입: 현재는 직접 메소드를 호출하지만, 향후 CommandBus와 Command Handler를 통한 CQRS 패턴 도입이 예정되어 있습니다.
  10. 데이터베이스 작업: 향후 구현될 로직에서는 Prisma Client를 사용하여 실제 데이터베이스에서 해당 userId와 관련된 모든 QuestionnaireRound 및 (연쇄적으로) QuestionnaireResponse 데이터를 삭제할 예정입니다.
  11. 결과 반환 및 오류 처리: 데이터 삭제 작업의 성공 또는 실패 결과가 역순으로 전파됩니다. QuestionnaireCrossEventReceiver에서 최종적으로 오류가 발생하면, 해당 오류를 로깅하고 필요시 Pub/Sub 시스템에 재처리를 요청할 수 있습니다.

5.5. 현재 구현 상태 및 향후 계획

현재 Questionnaire 도메인의 사용자 삭제 이벤트 처리는 다음과 같은 상태입니다:

  1. 이벤트 수신 구조: 완전히 구현됨

    • User 도메인에서 user.hard_deleteduser.soft_deleted 이벤트 발행
    • EventBridgeService를 통한 Pub/Sub 전달
    • API Gateway의 WebhooksController/EventsService를 통한 수신 및 재발행
    • QuestionnaireCrossEventReceiver에서 이벤트 구독
  2. 데이터 처리 로직: TODO 상태

    • handleUserQuestionnaireDataDeletion 메소드는 현재 로깅만 수행
    • 실제 데이터베이스 삭제 로직 구현 필요
  3. 향후 CQRS 패턴 도입 계획:

    • CommandBus 및 Command Handler 구조 도입
    • DeleteQuestionnaireDataCommand 구현
    • QuestionnaireResponseCommandRepository 구현
    • 트랜잭션 관리 및 데이터 일관성 보장

이러한 구조는 각 도메인의 책임을 명확히 하고, 도메인 간의 결합도를 낮추며, 향후 CQRS 패턴을 통해 쓰기 작업(데이터 삭제)을 명령으로 캡슐화하여 관리할 수 있도록 준비되어 있습니다.

6. 주요 고려 사항 (일반)

  • 멱등성(Idempotency): 이벤트 핸들러 및 커맨드 핸들러는 동일한 이벤트/커맨드를 여러 번 수신하더라도 시스템 상태가 일관되게 유지되도록 멱등성을 보장해야 합니다. EventsService 레벨에서의 eventId를 사용한 중복 제거가 1차 방어선 역할을 합니다.
  • 오류 처리 및 재시도: 메시지 브로커의 재시도 정책, 데드 레터 큐(DLQ), 핸들러 내부의 특정 오류에 대한 로직(예: 일시적 오류 시 재시도, 영구적 오류 시 로깅 및 알림)을 포함한 포괄적인 오류 처리 전략이 필요합니다.
  • 최종 일관성(Eventual Consistency): 이벤트 기반 시스템은 본질적으로 최종 일관성을 가집니다. 모든 관련 도메인이 특정 이벤트에 대한 처리를 완료하기까지 시간이 걸릴 수 있음을 인지하고 설계해야 합니다.
  • 이벤트 스키마 관리 및 버전 관리: 도메인 간에 공유되는 이벤트의 스키마는 명확히 정의되고 버전 관리되어야 합니다. 변경 발생 시 하위 호환성을 고려해야 합니다.
  • 트랜잭션 관리: 단일 서비스 내에서의 트랜잭션은 비교적 간단하지만, 여러 서비스에 걸친 분산 트랜잭션은 복잡합니다. Saga 패턴 등을 고려할 수 있으나, 가능하면 각 서비스가 자신의 데이터 일관성을 책임지도록 설계하는 것이 좋습니다.
  • 모니터링 및 로깅: 이벤트 흐름, 처리 시간, 오류 발생 등을 모니터링하고 상세한 로그를 기록하여 시스템 상태를 파악하고 문제를 진단할 수 있어야 합니다.