Spring camp 2017 참석 메모
· 약 4분
Project Reactive
- 스프링 5.0부터 지원됨
- 아직은 스냅샷이므로 일단 보류
CQRS
이벤트
- 불변 객체
- 오직 추가만 가능하게 보관
- 이벤트 스토어는 persistance하기만 하다면 무엇이든 상관없음
이벤트 리플레이
- 현재 상태를 복원하는 것
- 초기값으로부터 지금까지 발생한 이벤트를 모두 적용하여 마지막 결과를 이끌어냄
- 언제나 해야만 한다?
- 대량의 이벤트가 있다면 성능상 문제가 발생한다.
스냅샷
- 이벤트 식별자, 버전, 리플레이 끝난 마지막 도메인 객체를 저장
- 이벤트 저장 시점에 생성
- 시간 내지는 이벤트 발생 개수를 기준으로 생성
- 보통 in-memory 저장소 사용
- 이렇게 하면 리플레이로 인한 비용은 줄일 수 있겠지만, 특정 쿼리에서 너무 느려진다
CQRS 란?
- 이러한 이벤트 소싱의 성능 한계를 해결하기위해 나옴
- 명령과 조회의 책임 분리
- 커맨드 모델과 쿼리 모델 분리
- 사실상 이벤트 소싱에서 필수라고 보면 됨
Event Projection
- 커맨드 모델로 이벤트가 발생하면 이벤트 스토어에 저장하고 쿼리 모델로 보내서 쿼리 모델의 저장소를 업데이트 하는 과정
Aggregate
- 연관있는 엔티티의 집합. 여기에서는 특정 도메인 오브젝트에 대한 이벤트의 리스트
- 이 객체에서 버전을 가지고 있어서 이벤트를 저장할 때 race condition이 발생하면 버전 비교해서 예기치못한 상태 변경을 피할 수 있다. 마치 칼레이도처럼.
- 이벤트마다 처리하는 apply() 메소드 만들어서 처리?
- 비지니스 로직에서는 도메인 오브젝트의 상태를 변경하지 않는다. 오직 이벤트로만.
MSA + Event Source?
- 장애 전파? 아니 근데 어차피 다른 시스템이 정상적으로 작동하지 않는데 결과가 정상적으로 리턴되는게 맞나????
이벤트 소싱
- 상태를 변경할 때 따로 로그를 남기는 경우가 있지만 트랜잭션이 아니고 또 개발자가 선택한 정보만 남기기 때문에 데이터 유실 가능성이 있다.
- 이벤트 소싱은 도메인에서 발생하는 모든 액션을 이벤트로 바꾸고, 이벤트 저장소에 저장하고, 이벤트 핸들러에 의해서 상태가 변경되는 것.
- 전통적으로는 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의 유일성 제약을 걸 수가 없어서 데이터 중복 막기 쉽지 않고
- 이미 만들어져있는 도구가 적다.