UP | HOME

이벤트 소싱

Table of Contents

상태를 저장하지 않고 누적된 데이터를 상태로 삼는 방법

1. CRUD와의 비교

이벤트 소싱을 활용해 확장 가능한 사내 플랫폼 만들기 | 당근 SERVER 밋업 2회 - YouTube 05:43 ~

CRUD는…

Create
객체를 레코드로 추가함
Read
객체를 불러옴
Update
객체를 불러와서 수정 후 저장함
Delete
객체를 삭제함 (또는 삭제했다고 침; soft-delete)

이벤트 소싱은 단 두 가지 밖에 없음

  • 이벤트 객체를 레코드로 추가함
  • 이벤트 객체 목록을 가져와 순회하며 연산함

익숙한 CRUD는 아래처럼 구현함

Create
(생성) 이벤트 객체를 레코드로 추가함
Read
이벤트 객체 목록을 가져와 순회하며 연산함
Update
(갱신) 이벤트 객체를 레코드로 추가함
Delete
(삭제) 이벤트 객체를 레코드로 추가함

2. 구현

영속적 레이어에는 두 가지만 있으면 된다. 이벤트 목록과 스냅샷.

{NDC21-프로그래밍} 〈쿠키런: 킹덤〉 서버 아키텍처 뜯어먹기! - YouTube

아래는 쿠키런: 킹덤에서 사용하는 사례:

CREATE TABLE journals (
│ -- 이벤트가 구분되는 개체의 ID
│ aggregate_id VARCHAR(255) NOT NULL,
│ -- 이벤트 번호 (1부터 시작)
│ sequence_number INT8 NOT NULL,
│ -- 이벤트 타입 (어떤 객체로 역직렬화할지 결정)
│ manifest VARCHAR(255) NOT NULL,
│ -- protobuf 이벤트 바이너리
│ journal BYTEA NOT NULL,
│ -- 이벤트가 만들어진 시간
│ created_at TIMESTAMP NOT NULL,
│
│ PRIMARY KEY (aggregate_id, sequence_number)
);
CREATE TABLE snapshots (
│ -- 이벤트가 구분되는 개체의 ID
│ aggregate_id VARCHAR(255) NOT NULL,
│ -- 스냅샷이 반영하고 있는 마지막 이벤트 번호-- 예시) 100일 경우 100번 째 이벤트까지 리플레이한 상태
│ sequence_number INT8 NOT NULL,
│ -- 이벤트 타입 (어떤 객체로 역직렬화할지 결정)
│ manifest VARCHAR(255) NOT NULL,
│ -- protobuf 이벤트 바이너리
│ snapshot BYTEA NOT NULL,
│ -- 이벤트가 만들어진 시간
│ created_at TIMESTAMP NOT NULL,
│
│ PRIMARY KEY (aggregate_id, sequence_number)
);
  1. 목표 상태에 대한 Entity를 정의
  2. Event Repository를 정의
  3. 각각의 Event를 정의
  4. 각각의 Event에 대한 EventHandler 정의

3. 장단점

3.1. 장점

  • DB 사용이 단순해진다
    • 이벤트, 스냅샷 테이블만 있으면 된다
    • 콘텐츠 개발 시 DB 스키마를 수정할 필요가 없어진다
    • INSERT, SELECT 쿼리만 사용한다
    • 쿼리, 인덱스 등의 DB 성능 문제가 발생할 문제가 없다
    • DB 사용 패턴이 일정하므로 부하 예측도 쉽다
  • 상태 변화에 대해 immutable event로 저장되어 audit log로서의 기능을 수행할 수 있다
  • 상태 변화가 순서대로 저장되어 롤백이 쉽다
  • 각각의 Event 단위로 이슈 발생을 추적할 수 있다
  • 비동기 전환이 간단하다

3.2. 단점

  • DDD Aggregate로 모델링이 힘든 경우 이벤트 소싱도 어렵다
  • DB에서 직접 상태를 조회 & 변경하기 어렵다
    • 별도 툴 개발이 필수적이다
  • 로깅 및 데이터 분석으로 활용하기는 기대보다 어렵다1

4. 참고

Footnotes:

1

[NDC21-프로그래밍] 〈쿠키런: 킹덤〉 서버 아키텍처 뜯어먹기! 21:52

Author: 안녕

Created: 2024-12-10 Tue 22:08