EventSourcing & CQRS

[Event Sourcing & CQRS ] 실습

isshomang 2020. 9. 3. 23:08

Event-Driven MSA를 공부하기 위해 핵심이라 할 수 있는 Event Sourcing 및 CQRS 실습 부분을 https://www.youtube.com/watch?v=12EGxMB8SR8&t=2318s Youtube 동영상 보고 정리

Event Sourcing

이벤트 소싱에서 중요한 개념 2가지

  • 모든 상태 변화
  • 순서에 따라 이벤트 저장

1. 데이터 저장방식의 새로운 Pattern

은행 계좌 테이블 간략화.

만약 특정 계좌에 잔액을 변경해야한다면 update쿼리를 통하여 변경.

  • 전통적인 방법에서는 이전의 상태를 알 수 없음
  • 거래 내역테이블 (아래 테이블)을 두어 관리함

전통적인 방식에서 이력테이블을 두는 경우 위 그림과 같이 엄청나게 많은 테이블들이 만들어질 거임

2. 모든 상태변화를 Event로 관리

이벤트의 가장 큰 특징은 불변이고 추가만 가능합니다. (update, delete 안됨)

이벤트는 식별자, 타입, 버전, 발생시간, 내용 등으로 구성됨 → simple

3. 모든 Event는 순서대로 영구 저장소에 저장

이벤트 저장소는 순차적으로 영구 저장소에 저장됨

이벤트 스토어에 저장된 이벤트에서 어떻게 도메인 객체를 로딩할까?

이벤트 핸들러 메소드 → 발생한 이벤트의 값들을 도메인 객체에 적용 / 저장

헨들러 메소드를 사용해 순차적으로 도메인에 적용하면 최신상태를 로딩할수 있습니다.

  • Event Replay

저장된 이벤트가 많다면 그 수 만큼 event를 replay해야함 → very slow

성능 개선을 위해 이벤트 상태를 가지고 있는 snapshot 저장함

  • snapshot은 id, replay 된 이벤트 버전, 객체
  • 어떠한 기준을 두어 생성
  • 보통 in memory에 저장

위와 같은 요구사항을 실행하기 위해서는 각 객체 별로 이벤트들을 조회 → 매우 느림

이벤트 소싱만을 이용했을 때 조회 기능을 할 때 성능에 큰 이슈가 있을 수 있음 → cqrs 적용

이런식으로 읽기는 Query 쪽, 쓰기 작업은 Command에서 사용

Event Sourcing 구현

  • Axon Framework등으로 보다 손쉽게 구현이 가능
  1. Command 쪽에서 Request가 들어오면 Command 객체를 생성하고 Validation 처리
  2. 생성된 Command를 토대로 Service 클래스(Command Handler) 호출
    • 도메인 객체 생성
    • 객체에 대한 event 조회
  3. Aggregate에서 도멘인의 로직 수행, Event 객체 생성 후 리턴
  4. Command Handler에서 event 저장 및 snapshot 저장
  5. 비동기로 EventPublisher EventProject 실행

Command Model은 Request의 파라미터를 Validate 후 Command Model로 매핑

  • DTO와 비슷
  • 불변객체이기 때문에 public setter를 구현하지 않음

  • 전달된 Command를 토대로 Aggregate를 생성 하거나 EventHandler를 통해서 로딩
  • Aggregate의 비즈니스 로직 함수를 호출

find 메소드는

  • Aggregate 생성 / Event 조회 / Snapshot 조회 및 병합
  • snapshot은 캐쉬 같은 거
  • replay : 최신 상태의 도메인 상태

  • Aggregate : Entity + Value Object
  • 데이터 변경 시 처리되는 한 단위
  • 이벤트 객체를 다루는 handler method를 가짐
  • exectedVersion : 객체상태 일관성 보장을 위한 버전

사용자 두명이 동시에 column을 읽고 수량 변경을 한다면 ( 사용자 A : +10, 사용자 B : +15)

사용자 B의 요청은 A의 요청이 끝날 때까지 기다려짐

  • DB Lock

이벤트 소싱에서 해결 방책

똑같은 상황이지만 Aggregate에서 마지막으로 발생한 이벤트의 버전이 저장되어있음

사용자 A의 커밋이 끝나면 버전이 +1, 그런다음 B가 커밋할 때 버전이 달라지기 때문에 Exception 발생

replay 함수가 실행되면 Aggregate의 이벤트들을 순차적으로 순회하며 이벤트의 상태를 Aggregate에 적용하며 버전을 추가

  • apply 메소드는 event와 1대1로 매핑됨
  • 도메인의 객체 상태를 직접적으로 변경하지 않음

  1. 이벤트 스토에 이벤트 저장
  2. 저장대상 이벤트 목록 clear
  3. 스냅샷 정책에 따라 snapshot 저장

  • expectedVersion이 0잉닌 경우 = 신규 등록이 아닌경우
  • 버전이 맞지 않는 경우 Exception

  • Event Type은 Aggregate내 유일해야함 : 클래스명 나쁘지 않음
  • Aggregate을 serialize

  • 비즈니스 로직상 Event를 listeing해야 하는 다른 서비스들에게 전송

  • Query Model 전용 DB에 Aggregate 저장

  • 이벤트 들은 전혀 관계가 없음
  • 이력 저장 손쉽게 가능
  • 버그 발생 시 이벤트를 replay하여 손쉽게 처리 가능
  • 저장 기능만 있기 때문에 성능이 좋음

  • 구현 할게 많음
  • 프레임워크 부족

Event Sourcing in MSA

  • MSA환경이 아니라면 이벤트 소싱은 적용할 이유가 없음

  • MSA 기존구조랑 거의 차이가 없음

Member Service와 Order Service가 있다고 합시다.

Member Service에서 주문목록 조회 기능이 있는데, MSA에서는 흔히 Order Service로 부터 데이터를 가져옴.

만약 OrderService에 장애가 발생하면 사용자는 조회 불가능.

MSA 장점 중 하나는 :

  • 하나의 도메인에서 발생한 오류를 타 도메인으로 전파하지 않는다 > 장애격리
  • 하지만 물리적인 장애 전파는 막았으나, 논리적으로는 막지 못함

MemberService에서 주문에 대한 정보를 view store로 가지고 있는 것