event sourcing
이벤트 처리는 애플리케이션에서 발생하는 이벤트(상태 변경, 사용자 액션 등)를 비동기적으로 처리하기 위한 방법이다
이벤트 소싱은 애플리케이션의 상태를 이벤트 로그로 관리하여 이를 기반으로 상태를 복원하거나 변경 사항을 추적하는 설계 패턴이다
기본 메커니즘
시스템에서 발생하는 모든 상태 변화를 이벤트로 기록한다
데이터베이스에 엔티티의 최신 상태를 저장하는 대신, 해당 엔티티와 관련된 이벤트들의 로그를 저장한다
이벤트 로그를 재생(replay)하여 엔티티의 현재 상태를 계산한다
이벤트 소싱 구성 요소
이벤트
도메인 내에서 발생한 사실, 사용자의 요청(명령)을 처리한 결과
이벤트 스토어
이벤트 소싱에서 발생하는 모든 이벤트를 저장하고 관리하는 저장소
이벤트 스트림
특정 엔티티 또는 애플리케이션의 상태 변경을 나타내는 이벤트의 연속적인 흐름
이벤트 스토어에서 특정 엔티티의 이벤트를 순차적으로 읽어들이는 방식으로 구현된다 (시간/버전 순)
부분 스트림: 이벤트 스토어에서 필요한 부분만 스트림으로 가져올 수 있다
프로젝션
이벤트 데이터를 읽어와 도메인 모델의 상태를 계산하거나 조회 가능한 형태로 변환하는 작업
이벤트 소싱에서 프로젝션은 상태를 쿼리하거나 읽기 모델을 생성하기 위해 사용된다
단일 이벤트 스트림에서 여러 프로젝션을 생성할 수 있다
cqrs를 통해 명령과 쿼리 모델을 분리하여 성능을 향상시킬 수 있다
구독
이벤트를 실시간으로 소비하는 메커니즘을 의미한다
다른 시스템이나 서비스가 이벤트 스트림을 수신하고 처리할 수 있도록 한다
비동기 처리: 이벤트가 발생하면 비동기적으로 구독자에게 전달
브로드캐스트: 여러 구독자가 동일한 이벤트를 수신
이벤트 기반 아키텍처, 알림 시스템 등에서 활용한다
구성 요소 간 동작 과정
이벤트 스토어가 이벤트를 저장한다
이벤트 스토림을 통해 엔티티별 이벤트를 재생하여 상태를 복원하거나 실시간 이벤트를 전달한다
프로젝션은 이벤트 스트림을 읽어 데이터를 집계하거나 읽기 모델로 변환한다
구독을 통해 다른 시스템이나 모듈이 이벤트를 실시간으로 받아 처리한다
온라인 쇼핑몰 주문 시스템의 주문 처리에 대한 이벤트 소싱 활용 예시
이벤트 스토어: 주문 생성(OrderCreated), 결제 완료(PaymentProcessed), 배송 완료(OrderShipped) 등의 이벤트를 저장한다
이벤트 스트림: 특정 주문 id의 이벤트 스트림을 재생하여 주문의 현재 상태를 복원한다
프로젝션: 주문 상태별 집계 데이터(배송 대기 중인 주문 수 등)를 생성하여 관리자 대시보드에 표시한다
구독: 배송 시스템이 OrderShipped 이벤트를 구독하고 배송 작업을 자동으로 시작한다
이벤트 구성 요소
이벤트 이름: 이벤트가 나타내는 동작을 명시한다 (OrderPlaced, ProductAddedToCart 등)
이벤트 데이터: 이벤트와 관련된 상세 정보를 나타낸다 (주문 id, 상품 정보 등)
타임스탬프: 이벤트 발생 시간
버전 정보(옵션): 이벤트를 순서대로 적용하기 위한 버전 관리
이벤트 소싱 동작 원리
명령으로부터 이벤트 생성
사용자의 요청은 명령(command)로 표현되며 명령이 성공적으로 처리되면 이벤트로 기록된다
이벤트 저장소의 이벤트 관리
이벤트는 순차적으로 저장된다
이벤트 저장소는 불변이며 기존 이벤트는 수정되지 않는다
즉, 사용자의 명령을 처리하면서 변경되는 상태값을 기존 이벤트의 값으로 수정하지 않고 새로 이벤트를 생성하여 반영한다
장바구니 이벤트 -> 주문 이벤트 -> 결제 이벤트 -> 주문 완료 이벤트
상태 복원
애플리케이션이 초기화되거나 특정 시점의 상태를 확인하려면 저장된 이벤트를 처음부터 재생하여 상태를 복원한다
스냅샷
이벤트 로그가 길어질 경우 성능 문제를 해결하기 위해 특정 시점의 상태를 저장한 스냅샷을 활용한다
스냅샷 이후의 이벤트만 재생하면 상태를 빠르게 복원할 수 있다
장점
데이터 무결성 및 추적 가능성
모든 상태 변경이 기록되므로 데이터의 변경 이력을 완벽히 추적할 수 있다
감사 및 디버깅
이벤트 로그를 분석하거나 과거 상태를 복원하여 문제의 원인을 파악할 수 있다
비동기 처리
이벤트를 다른 서비스나 시스템과 비동기로 처리할 수 있기에 확장성과 성능 향상이 가능하다
CQRS와의 결합
이벤트 소싱은 CQRS(Command Query Responsibility Segregation) 아키텍처와 잘 어울리며 읽기/쓰기 모델을 분리하여 성능과 복잡성을 개선할 수 있다
단점
복잡성
애플리케이션 설계 및 구현이 복잡해진다 (특히 이벤트 버전 관리와 이벤트 재생 로직)
성능 문제
상태 복원을 위해 모든 이벤트를 재생해야 하므로 이벤트 로그가 많아질수록 성능이 저하될 수 있다 (스냅샷 필요)
데이터 정합성
이벤트 저장소의 불변성이 유지되지 않거나 이벤트의 순서가 잘못되면 데이터 정합성 문제가 발생할 수 있다
이벤트 스키마 관리
이벤트 모델의 변경(필드 추가/삭제 등)은 이벤트 저장소와 시스템에 영향을 미치므로 신중히 관리해야 한다
이벤트 소싱 도구
이벤트 저장소
eventstoredb
이벤트 소싱 전용 데이터베이스
이벤트를 순차적으로 저장하고 조회할 수 있도록 설계되었다
apache kafka
분산 메시징 플랫폼으로 이벤트 스트림을 저장하고 처리하기 적합하다
- 이벤트가 스트림으로 저장된다 (상태 복원에 유용)
- 이벤트 저장과 스트림 처리 모두 지원
- kafka streams, ksql을 사용해서 이벤트 처리 가능
spring for apache kafka
postgresql + json/jsonb
rdbms 기반 이벤트 저장소 활용
- json/jsonb 컬럼을 사용해 이벤트 데이터 저장
- 기존 rdbms 환경을 그대로 활용할 수 있다
spring data jpa/jdbc
이벤트 처리 및 cqrs 프레임워크
axon server
axon framework와 함게 제공되는 전용 이벤트 저장소
- 이벤트 저장, cqrs, ddd 지원
- axon server외에도 다른 db를 이벤트 저장소로 설정 가능
spring boot와 자연스럽게 통합할 수 있다
- axon에서 spring boot starter를 지원한다
eventuate
이벤트 소싱과 cqrs를 지원하는 플랫폼이다
- 이벤트 스토어와 메시징 시스템을 함게 제공
- mysql, postgresql 등을 이벤트 저장소로 활용 가능
- saga 패턴 지원
spring cloud stream
이벤트 기반 애플리케이션을 쉽게 구축하도록 지원하는 스프링 모듈
- apache kafka, rabbitmq와 같은 메시징 시스템과 통합
- 이벤트 처리 파이프라인 구축에 유용하다
스냅샷 관리
axon framework
스냅샷 기능 내장
스냅샷을 특정 간격으로 자동 생성
직접 구현
postgresql, mongodb와 같은 데이터베이스를 사용해 스냅샷을 직접 구현한다
spring batch로 주기적인 스냅샷 생성 스케줄링
데이터 처리 및 변환
kafka streams
- 이밴트 스트림을 변환하고 집계
- spring kafka와의 통합
테스트 및 모니터링 도구
testcontainers
이벤트 저장소나 메시징 시스템을 컨테이너로 실행하여 테스트 환경 제공
micrometer
이벤트 처리 시스템의 모니터링 및 메트릭 수집