이벤트 소싱
- 상태를 변경할 때 따로 로그를 남기는 경우가 있지만 트랜잭션이 아니고 또 개발자가 선택한 정보만 남기기 때문에 데이터 유실 가능성이 있다.
이벤트 소싱은 도메인에서 발생하는 모든 액션을 이벤트로 바꾸고, 이벤트 저장소에 저장하고, 이벤트 핸들러에 의해서 상태가 변경되는 것.
- 전통적으로는 state를 저장. 이벤트 소싱에서는 이벤트를 저장.
- 예를 들어 장바구니 기능이라면 전통적으로는 장바구니의 상태를 저장하지만 이벤트소싱은 아이템 추가, 삭제에 대한 이벤트만 저장하고 결과가 필요하면 리플레이를 돌려서 상태를 생성하게끔 함.
도메인 오브젝트 하나당 하나의 이벤트 스트림
커맨드가 들어오면 validation을 거쳐서 이벤트를 생성 후 이벤트 저장소에 저장. 만약에 수행 불가능한 상태라면 오류 반환
이벤트 자체는 돌이킬 수 없는 사실. 불변 객체. -> 따라서 항상 성공하며, 과거형을 사용한다.
결과를 만들땐, 초기값으로부터 버전 n까지 모든 이벤트를 순차적으로 집계한 결과. 즉 최신이라면 모든 이벤트를 불러와서 적용시키면 된다.
- 이벤트를 저장하면서 동시에 핸들러로 보내서 상태를 변경하게 함.
- 이렇게 되면 결국 이것도 원자성은 없어지는게 아닌가?
- 보통 RDB에 저장할 땐 키에는 오브젝트 아이디와 버전, 밸류에는 이벤트 타입과 직렬화된 전체 페이로드
- 아주 오랫동안 살아남은 도메인 오브젝트가 있다면?
- 엄청난 갯수의 이벤트를 가지고 있음
- 덕분에 성능에 굉장한 영향을 끼침
- 저장하는데도 큰 용량을 차지함
- 1 <= m <= n 일 때, state(n) = state(m) + eventIterator(m, n)
- 칼레이도에서 봤던 것처럼 일종의 스냅샷으로 바꿔둘 수 있지 않을까?
- 그래서 별도의 스냅샷 저장소를 두고 거기에 스냅샷을 저장해둔다.
- 분산 시스템에는 두가지 어려운 문제가 있음. 메세지가 중복될 수 있고 순서를 보장할 수 없음.
- 이벤트 소싱을 하더라도 정확히 메세지를 1번 전달하는 것은 해결하기 힘든 문제임.
- 그에 대한 대안으로 적어도 1번 전달을 사용함. 이 경우 메세지가 중복될 수 있어서 메세지의 멱등성을 보장해야만 함.
- 즉, add(5) 이런 이벤트가 되면 멱등성을 보장할 수 없기 때문에 set(15)와 같은 이벤트가 되어야 함.
- 이벤트 소싱을 쓰게 되면 모든 이벤트가 저장되므로 이 경우 유리함. 전통적 방식이면 일부 set 이벤트를 유실할 수도
- 이벤트 스트림 - 메세지 브로커 - 컨슈머 구조? 이렇게 되면 1 이벤트에 1 컨슈머를 보장하는게 가능함.
CQRS (Command Query Responsibility Segregation)
- 만약 재고 10개 미만 상품 목록을 알려면?
- 이벤트 저장소 풀 스캔하여 상품 하나하나 복원하여 고르면 되긴 하겠지만... 성능하고 어쩔
- 따라서 이론적으로는 이벤트 소싱은 CQRS에 독립적이지만 그렇게 되면 위와 같은 케이스에 대응할 수가 없음.
- 원래는 CQS라는게 있었음. 즉, 쿼리가 상태를 변경하지 않는다. 어떤 의미에서는 getter, setter같기도. 메소드를 두 그룹으로 나눠서 하나는 결과만 리턴하고 하나는 상태만 변경하고.
- CQRS는 CQS에서 더 나아가서 객체 단위에서 나눠버리는 것. 그 이상으로 가면 아예 시스템, 어플리케이션단위에서 구분해버리는 것.
- 즉 Command Side, Query Side 두 시스템으로 나누어짐.
- Command side는 클라이언트에서 커맨드가 들어오면 도메인 레이어에서 이벤트 스토어에 저장하기만 함. 그 이후에는 Query side로 이벤트 전송
- Query Side는 이벤트들을 받아다가 뷰를 만들고 유지함. 덕분에 오직 조회 편의성만 신경씀. 클라이언트가 뭔가 데이터가 필요하면 여기로 보내서 받아감.
- 하나의 read 모델이 여러 도메인의 이벤트 스토어를 가지고 있을 수 있음. 이렇게 되면 조인같은게 쉽게 가능.
- 또한 하나의 도메인 이벤트 스토어는 어떻게 저장하든 그 형식에 구애받지 않음.
단점
- 익숙하지 않고
- 학습 곡선이 가파르고
- 모든 과정이 비동기이고
- eventual consistency이기 때문에 중간중간에 맞지 않을 수 있고
- 과도한 엔지니어링이 발생하므로 모든 곳에 적용하려고 하지 말고.
- RDB의 유일성 제약을 걸 수가 없어서 데이터 중복 막기 쉽지 않고
- 이미 만들어져있는 도구가 적다.