IT개발

Event Sourcing + CQRS 구현 전략과 Kafka Streams 연동

우리모두 개발자되기 2025. 4. 27. 17:48

 

Event Sourcing + CQRS 구현 전략과 Kafka Streams 연동

마이크로서비스 아키텍처가 확산되면서 시스템의 복잡성이 증가함에 따라, 데이터 일관성과 확장성을 동시에 만족시키기 위한 다양한 패턴이 주목받고 있습니다. 그중에서도 Event Sourcing과 **CQRS(Command Query Responsibility Segregation)**는 핵심적인 역할을 담당하는 아키텍처 패턴입니다. 여기에 Kafka Streams를 결합하면 더욱 견고하고 실시간성이 뛰어난 시스템을 구축할 수 있습니다. 이번 글에서는 Event Sourcing과 CQRS의 기본 개념을 정리하고, Kafka Streams를 활용하여 이들을 통합하는 전략을 심도 깊게 설명하겠습니다.


1. Event Sourcing 기본 개념

1.1 정의

Event Sourcing은 시스템의 현재 상태를 직접 저장하지 않고, 과거에 발생한 **모든 이벤트(event)**를 저장하는 방식입니다. 시스템 상태는 이벤트를 순서대로 재생(replay)하여 복원할 수 있습니다.

1.2 주요 특징

  • 이벤트 불변성: 한번 기록된 이벤트는 수정하거나 삭제하지 않음.
  • 완벽한 감사 추적(audit trail): 모든 변경 이력이 기록됨.
  • 시간 이동(Time Travel): 특정 시점의 상태를 재구성 가능.

1.3 기본 흐름

  1. 사용자의 요청을 이벤트로 변환
  2. 이벤트 저장소(Event Store)에 기록
  3. 이벤트를 기반으로 애그리게이트(Aggregate) 상태 업데이트

2. CQRS 기본 개념

2.1 정의

**CQRS(Command Query Responsibility Segregation)**는 **명령(Command)**과 **조회(Query)**를 분리하는 아키텍처 패턴입니다. 쓰기 모델과 읽기 모델을 별도로 유지함으로써 확장성과 최적화를 극대화할 수 있습니다.

2.2 주요 특징

  • 명령과 조회 분리: 다른 데이터 모델, 다른 데이터 저장소 사용 가능
  • 성능 최적화: 조회용 데이터는 별도로 최적화 가능
  • 복잡성 증가: 모델이 두 개가 되므로 관리 포인트 증가

2.3 기본 흐름

  • Command Side: 사용자의 입력 처리 및 이벤트 생성
  • Query Side: 이벤트를 기반으로 읽기 모델 업데이트 및 데이터 제공

3. Event Sourcing과 CQRS 결합 전략

Event Sourcing은 자연스럽게 CQRS와 결합될 수 있습니다. Command Side는 이벤트를 생성하고 저장하고, Query Side는 이벤트를 구독하여 별도의 읽기 모델을 구축합니다.

3.1 시스템 구성도

[Client] → [Command API] → [Event Store] → [Event Processor] → [Query DB] → [Query API]

3.2 데이터 흐름

  1. Client가 Command API에 요청
  2. Command API가 유효성 검사 후 이벤트 생성
  3. 이벤트를 Event Store에 저장
  4. Event Processor가 이벤트를 구독 및 처리
  5. Query Database를 업데이트
  6. Client는 Query API를 통해 조회

4. Kafka Streams를 이용한 이벤트 처리

4.1 Kafka와 Event Sourcing

Apache Kafka는 분산 스트리밍 플랫폼으로, 이벤트 소싱에 최적화된 시스템입니다. 이벤트는 Kafka Topic에 저장되고, 이를 기반으로 다양한 애플리케이션이 구독하고 상태를 구성할 수 있습니다.

4.2 Kafka Streams란?

Kafka Streams는 Kafka 위에서 동작하는 클라이언트 라이브러리로, 분산 데이터 처리를 간편하게 구현할 수 있게 해줍니다.

특징

  • 상태 기반(stream-table duality) 연산 가능
  • 실시간 이벤트 프로세싱
  • 고가용성과 확장성 지원

4.3 Kafka Streams 구조

  • Stream: 연속된 이벤트 스트림
  • Table: 현재 상태를 나타내는 Materialized View
  • Processor API: 직접 노드 기반 데이터 흐름 제어 가능
  • DSL: 고수준 API를 통해 데이터 변환, 집계 가능

5. Event Sourcing + CQRS + Kafka Streams 연동 전략

5.1 연동 아키텍처

[Client]
   ↓
[Command API] → [Kafka Topic (Events)] → [Kafka Streams Processor] → [Materialized Query DB]
   ↓
[Event Store (Optional)]

5.2 주요 흐름 상세

  1. Command 발행
    • Command API가 입력 검증 후 이벤트 생성
    • 이벤트를 Kafka Topic에 Publish
  2. 이벤트 저장
    • Event Store(별도 저장소) 또는 Kafka의 로그를 영구 저장소로 활용
  3. Kafka Streams 처리
    • Kafka Streams가 Topic을 구독하여 이벤트 처리
    • 필요 시 이벤트를 Materialized View 형태로 Query Database에 저장
  4. Query API 제공
    • 최적화된 읽기 모델을 통해 고속 데이터 조회 지원

6. 기술적 구현 요소

6.1 이벤트 저장 설계

  • Kafka Topic을 Event Log로 간주
  • 각 이벤트는 Schema Registry로 스키마 관리(Avro, Protobuf)

6.2 Command Validation

  • 유효성 검사 후 이벤트로 변환
  • 불변 객체(Immutable Event) 생성

6.3 Kafka Streams Application

  • Stream을 Table로 변환(Materialized View 구축)
  • KTable을 통해 이벤트 Replay 없이 빠른 조회
  • GlobalKTable 사용 시 전역 조회 모델 구성 가능

6.4 장애 복구 및 스냅샷

  • 이벤트가 많아질 경우 Snapshot Event 생성
  • Snapshot 이후 이벤트만 Replay하여 성능 최적화

6.5 데이터 일관성 보장

  • Exactly-once 처리 보장(Kafka Streams 설정 필요)
  • 이벤트 순서(Ordering) 관리: Partition Key 적절 설정

7. 예제: 간단한 주문 시스템(Order System)

Command Side

  • 사용자가 "CreateOrder" 요청
  • OrderCreated 이벤트 생성 및 Kafka Publish

Kafka Streams

  • OrderCreated 이벤트를 읽어 새로운 주문 테이블(Order Table) 업데이트

Query Side

  • REST API가 Order Table을 조회하여 주문 상태 제공

코드 스니펫 예시

KStream<String, OrderEvent> orderEvents = builder.stream("order-events");

orderEvents
  .filter((key, event) -> event.getType().equals("OrderCreated"))
  .mapValues(event -> new OrderView(event.getOrderId(), event.getCustomerId(), "CREATED"))
  .to("order-views", Produced.with(Serdes.String(), new OrderViewSerde()));

8. 실전 적용 시 주의사항

  • 이벤트 스키마 진화(Backward Compatibility) 설계 필수
  • 이벤트 중복, 재처리 대응 전략 필요
  • 데이터 적재량 증가에 따른 Kafka 클러스터 확장성 고려
  • 운영 모니터링: Kafka Streams 상태 모니터링 필요
  • 테스트 전략 강화: 이벤트 기반 테스트 작성 필수

9. 결론

Event SourcingCQRS는 데이터 일관성과 확장성을 확보할 수 있는 매우 강력한 패턴입니다. 여기에 Kafka Streams를 연계하면 실시간 이벤트 처리와 조회 모델 구축이 간편해지며, 대규모 트래픽과 높은 가용성이 요구되는 시스템에서도 뛰어난 성능을 발휘할 수 있습니다.

하지만 구현과 운영 복잡도가 증가하는 만큼, 이벤트 설계, 데이터 일관성 관리, 장애 대응 전략을 면밀히 수립해야 합니다. 체계적인 설계와 준비를 통해 Event Sourcing + CQRS + Kafka Streams 기반 아키텍처를 성공적으로 도입해보시기 바랍니다.