Event Notification
이는 한 시스템이 다른 시스템에 도메인의 변경 사항을 알리기 위해 이벤트 메시지를 보낼 때 발생한다. 이벤트 알림의 핵심 요소는 소스 시스템이 응답에 크게 신경 쓰지 않는다는 것이다. 종종 응답을 전혀 기대하지 않거나, 소스에서 관심을 갖는 응답이 있더라도 간접적인 경우가 많다. 이벤트를 전송하는 로직 흐름과 해당 이벤트에 대한 어떤 반응에 응답하는 로직 흐름 사이에는 뚜렷한 분리가 있을 것이다.
이벤트 알림은 결합 수준이 낮고 설정이 매우 간단하기 때문에 좋다. 그러나 다양한 이벤트 알림을 통해 실행되는 논리적 흐름이 실제로 존재한다면 문제가 될 수 있다. 문제는 프로그램 텍스트에 명시되어 있지 않기 때문에 이러한 흐름을 파악하기 어려울 수 있다는 것이다. 이러한 흐름을 파악할 수 있는 유일한 방법은 실시간 시스템을 모니터링하는 것뿐인 경우가 많다. 따라서 이러한 흐름을 디버깅하고 수정하기가 어려울 수 있다. 더 큰 규모의 흐름을 놓치고 있다는 사실을 깨닫지 못한 채 이벤트 알림을 통해 멋지게 분리된 시스템을 만들기가 매우 쉬워 향후에 문제가 발생할 수 있다는 위험이 있다. 이 패턴은 여전히 매우 유용하지만 함정에 주의해야 한다.
이 함정의 간단한 예는 이벤트가 패시브-어그레시브 커멘드(passive-aggressive command)으로 사용되는 경우다. 소스 시스템에서 수신자가 어떤 작업을 수행할 것으로 예상하고 그 의도를 보여주기 위해 명령 메시지를 사용해야 하지만 대신 메시지 스타일을 이벤트로 지정할 때 이런 일이 발생한다.
즉, 시스템 A가 시스템 B에게 어떤 작업을 수행하라고 요청하면서도, 이를 명시적 명령이 아닌 이벤트로 전달하는 경우를 말한다. 이러한 접근은 시스템 간의 책임과 기대를 모호하게 만들 수 있다.
이벤트에는 많은 데이터를 담을 필요는 없으며, 보통 아이디 정보와 자세한 정보를 조회할 수 있는 발신자로 연결되는 링크만 있으면 된다. 수신자는 무언가 변경되었음을 알고 변경의 성격에 대한 최소한의 정보를 얻을 수 있지만, 다음에 수행할 작업을 결정하기 위해 발신자에게 다시 요청을 보낸다.
Event-Carried State Transfer
이 패턴은 시스템의 클라이언트가 추가 작업을 하기 위해 소스 시스템에 연락할 필요가 없는 방식으로 업데이트하려는 경우에 나타난다. 고객 관리 시스템은 고객이 주소와 같은 세부 정보를 변경할 때마다 변경된 데이터의 세부 정보가 포함된 이벤트와 함께 이벤트를 실행할 수 있다. 그러면 수신자는 자신의 고객 데이터 사본을 변경 사항으로 업데이트할 수 있으므로 향후 작업을 수행하기 위해 주 고객 시스템과 통신할 필요가 없다.
이 패턴의 명백한 단점은 많은 데이터가 돌아다니고 사본이 많다는 것이다. 하지만 스토리지가 풍부한 시대에는 이런 문제가 덜하다. 고객 시스템을 사용할 수 없게 되더라도 수신자 시스템이 작동할 수 있기 때문에 복원력이 향상된다. 고객 정보에 액세스 하기 위해 원격으로 통화할 필요가 없으므로 대기 시간이 단축된다. 모든 고객 시스템의 쿼리를 처리하기 위해 고객 시스템의 부하를 걱정할 필요가 없다. 하지만 일반적으로 필요할 때 발신자에게 호출하여 추가 정보를 요청하는 것이 더 쉬울 때 수신자는 모든 상태를 유지 관리해야 하기 때문에 더 복잡해진다.
Event-Sourcing
이벤트 소싱의 핵심 아이디어는 시스템 상태를 변경할 때마다 해당 상태 변경을 이벤트로 기록하고, 향후 언제든지 이벤트를 재처리하여 시스템 상태를 확실하게 재구축할 수 있다는 것이다. 이벤트 저장소가 진실의 주요 소스가 되고 시스템 상태는 순전히 여기에서 파생된다. 프로그래머에게 가장 좋은 예는 버전 제어 시스템이다. 모든 커밋의 로그가 이벤트 저장소이고 소스 트리의 작업 복사본이 시스템 상태다.
이벤트 소싱은 많은 문제를 야기하지만 여기서는 다루지 않겠지만 몇 가지 일반적인 오해를 강조하고 싶다. 이벤트 처리가 비동기적일 필요는 없다. 로컬 git 리포지토리를 업데이트하는 경우를 생각해 보자. 이는 서브버전과 같은 중앙 집중식 버전 제어 시스템을 업데이트하는 것과 마찬가지로 전적으로 동기적인 작업이다. 물론 이러한 모든 커밋을 사용하면 모든 종류의 흥미로운 작업을 할 수 있으며, git이 좋은 예이지만 핵심 커밋은 근본적으로 간단한 작업이다.
또 다른 일반적인 실수는 이벤트 소스 시스템을 사용하는 모든 사람이 유용한 데이터를 파악하기 위해 이벤트 로그를 이해하고 액세스해야 한다고 가정하는 것이다. 하지만 이벤트 로그에 대한 지식은 제한적일 수 있다. 소스 트리의 모든 커밋을 알지 못하는 편집기에서 이 글을 작성하고 있기 때문에 디스크에 파일이 있다고 가정할 뿐이다. 이벤트 소스 시스템의 많은 처리는 유용한 작업 복사본을 기반으로 할 수 있다. 이벤트 로그의 정보가 실제로 필요한 요소만 이벤트 로그를 조작해야 한다. 도움이 된다면 스키마가 다른 여러 개의 작업 복사본을 가질 수 있지만, 일반적으로 도메인 처리와 이벤트 로그에서 작업 복사본을 도출하는 작업은 명확하게 구분되어야 한다.
이벤트 로그로 작업할 때는 작업 복사본이 필요할 때마다 모든 이벤트를 처음부터 처리할 필요가 없도록 작업 복사본의 스냅샷을 만드는 것이 유용할 때가 많다. 이벤트 로그를 변경 사항 목록으로 볼 수도 있고 상태 목록으로 볼 수도 있는 이중성이 있다. 둘 중 하나를 다른 것에서 파생할 수 있다. 버전 제어 시스템은 최상의 성능을 얻기 위해 이벤트 로그에 스냅샷과 델타를 혼합하여 사용하는 경우가 많다.
이벤트 소싱은 버전 관리 시스템의 가치를 생각할 때 쉽게 떠올릴 수 있는 여러 가지 흥미로운 이점이 있다. 이벤트 로그는 강력한 감사 기능을 제공한다(회계 거래는 계정 잔액에 대한 이벤트 소스입니다). 이벤트 로그를 특정 지점까지 재생하여 과거 상태를 재현할 수 있다. 재생할 때 가상의 이벤트를 삽입하여 대체 기록을 탐색할 수도 있다. 이벤트 소싱을 사용하면 메모리 이미지와 같이 내구성이 없는 작업 복사본도 그럴듯하게 만들 수 있다.
이벤트 소싱에는 문제가 있다. 이벤트 재생은 결과가 외부 시스템과의 상호 작용에 따라 달라질 때 문제가 된다. 시간이 지남에 따라 이벤트 스키마가 변경될 때 어떻게 대처해야 하는지 알아내야 한다. 많은 사람들이 이벤트 처리가 애플리케이션에 복잡성을 더한다고 생각한다(작업 복사본을 도출하는 구성 요소와 도메인 처리를 수행하는 구성 요소가 제대로 분리되지 않았기 때문이 아닌가 하는 생각이 들기도 하지만요).
CQRS
명령 쿼리 책임 분리(CQRS)는 정보를 읽고 쓰는 데이터 구조를 분리하는 개념이다. 디자인에 이벤트가 없어도 CQRS를 사용할 수 있기 때문에 엄밀히 말해 CQRS는 이벤트에 관한 것이 아니다. 하지만 일반적으로 CQRS와 앞의 패턴을 결합하는 경우가 많다.
복잡한 도메인에서 읽기와 쓰기를 모두 처리하는 단일 모델은 너무 복잡해지기 때문에 모델을 분리하여 단순화할 수 있다는 것이 CQRS의 정당성이다. 이는 특히 읽기는 많고 쓰기는 거의 없는 등 액세스 패턴에 차이가 있을 때 유용하다. 하지만 CQRS를 사용하면 얻을 수 있는 이점과 모델 분리로 인한 추가적인 복잡성의 균형을 맞춰야 한다.
Making sense of these patterns
일종의 소프트웨어 엔지니어로서 샘플 수집에 관심이 많은 저는 이것은 까다로운 지형이라고 생각한다. 핵심 문제는 서로 다른 패턴을 혼동하는 것이다. 한 프로젝트에서 유능하고 경험이 풍부한 프로젝트 관리자는 이벤트 소싱이 재앙이었다고 말했는데, 변경 사항이 있을 때마다 읽기 및 쓰기 모델을 모두 업데이트하는 데 두 배의 작업이 필요했다고 한다. 그 문구만으로도 이벤트 소싱과 CQRS 사이의 잠재적인 혼동을 감지할 수 있는데, 어느 것이 원인인지 어떻게 파악할 수 있을까? 프로젝트의 기술 책임자는 복잡성을 높이는 것으로 알려진 많은 비동기 통신이 주된 문제라고 주장했지만, 이벤트 소싱이나 CQRS의 필수적인 부분은 아니었다. 또한 이러한 모든 패턴은 적재적소에 사용하면 좋지만 잘못된 지형에 배치하면 좋지 않다는 점에 유의해야 한다. 하지만 패턴을 조합할 때 올바른 지형이 무엇인지 파악하기는 어렵다.
'개발' 카테고리의 다른 글
레디스 센티널 정리 (0) | 2025.05.09 |
---|---|
레디스 복제 정리 (0) | 2025.05.08 |
메시지 큐와 이벤트 스트림 (0) | 2025.05.02 |
배치 처리와 스트림 처리 (0) | 2025.04.30 |
[kotest] Add assertion error message 기여 과정 (0) | 2025.04.04 |