본문 바로가기
Java

Commands and Events(Design Principles of Reactive Systems)

by ybs 2021. 12. 22.
반응형

Chapter1 에 이런 글이 있다. "With Reactive, you put the notion of events at the core of your system."

reactive 프로그래밍을 통해 reactive 한 시스템을 만드는 것은 event driven 이 되는것과 관련이 있다.

 

Commands and Events

먼저 commands 와 events 차이에 대해서 논의해보자. 이 두개는 핵심적인 기본 개념이다(fundamental). 

 

Commands

모든 시스템은 명령(commands)을 내린다. 명령(commands)은 사용자가 수행하고자 하는 action이다. 대부분의 HTTP 기반 API들은 클라이언트가 요청하는 명령(commands)을 전달한다. 여기서 action이 아직 수행되지 않았다는 것을 이해하는 것이 중요하다. 미래에 수행될수도 있고 아예 수행되지 않을수도 있다. 또한 성공할 수도 있고 실패할 수도 있다. 일반적으로 명령(commands)은 특정 수신자에게 전송되고 결과는 다시 클라이언트에게 전달된다.

 

간단한 HTTP 요청을 보낸다면 명령(commands) 이고, 애플리케이션은 명령(commands)을 받고 처리해서 결과를 생성한다.

Events

이벤트(events)는 성공적으로 완료된 action이다. 이벤트(events)는 키입력, 실패, 주문, 시스템에 중요한 모든것 등 발생한 fact 를 나타낸다. 이벤트(events)는 명령(commands) 에 의해 수행된 작업 결과가 될 수 있다.

 

cf) 이벤트를 '성공적으로 완료된' 이라고 표현했는데 성공이라는 개념을 이벤트 발행(publish), 소비(consume) 까지 완료된거로 생각하면 오해가 생길 수 있다. 이벤트를 발행하는 곳에서는 누가 어떻게 소비하는지 알 수 없다. 여기서 '성공적' 이라고 표현한것은 명령(commands) 과 대비되는 부분을 설명하기 위해 쓴 표현이라고 이해했다.

 

HTTP 요청 상황을 볼 때, 요청을 보낸 후 일단 응답이 작성되면(written) 이벤트(events)가 된다. 그 이벤트(events)는 로그를 남기는데 쓰이거나 관심있는 파티(broadcast to interested parties)에 알려서 무슨일이 일어났는지 알 수있게 한다.

 

이벤트(events)는 불변(immutable) 하다. 이벤트(events)를 삭제할 수 없다. 즉 과거를 바꿀 순 없다. 만약 이전에 보낸 fact를 반박하고 싶다면 fact가 틀렸다는 또다른 이벤트(events)를 보내야 한다. 기존에 전달된 fact 들은 현재 지식을 올바르게 세우는 다른 fact 에 의해서만 무관하게 된다.

 

명령(commands) 과 이벤트(events)는 모두 메세지다. 그런데 성격이 매우 다르다. 명령(commands) 은 검증대상이다. 이 명령이 올바른지 아닌지를 검증해야 하고 실패할 가능성이 있다. 그래서 action 이 아직 수행되지 않았다고 표현한거다. 명령은 일반적으로 명령형 동사를 써서 이름 짓는다. 이벤트(events) 는 이미 벌어진 돌이킬 수 없는 사실이다. 그래서 검증대상이 아니다. 이벤트(events) 는 이름 지을 때 과거형 동사를 사용한다.

스프링캠프2017 이벤트 소싱 소개(이규원님)

 

Messages

이벤트(events)는 어떻게 발행할까? 많은 방법이 있지만 kafka 와 activeMQ 가 유명하다. producers 와 consumers 사이에 brokers 역할을 한다. 본질적으로 이벤트(events)는 topics 이나 queues 에 쓰여진다. 이벤트(events)를 발행하기 위해 애플리케이션은  구체적인 destination(topic 이나 queue) 을 대상으로 message 를 broker 에 보낸다. 

 

cf) Spring Cloud Stream binder Kafka 를 사용하면 spring.cloud.stream.bindings 설정을 하는데 destination 에 kafka topic 을 넣는다. spring.cloud.stream.bindings 설정은 kafka 말고 rabbitMQ 구현체도 지원하고 있기에 kafka 에 종속적인 topic 이란 네이밍을 안쓰고 destination 네이밍을 사용한다.

spring:
  cloud:
    stream:
      bindings:
        ybs-channel-event:
          destination: ybs-kafka-topic-name
          producer:
            partition-count: 10

 

 

message 는 이벤트와 관련된 디테일한 것(누가 발행했는지, 언제 발행했는지, 잠재적으로 유니크한 ID 등) 을 설명하는 self-contained data structure 다. 일반적으로 이벤트 자체를 비니지스 중심(business-centric) 으로 유지하고 기술적 측면에서는 추가적인 metadata를 사용하는게 좋다.

 

이벤트를 consume 하는쪽에서는 관심있는 topic 이나 queue 를 subscribe 하고 message 를 받는다. 이벤트를 unwrap 해서 관련 metadata(이벤트 발행시간, 발생한 위치 등)도 가져올 수 있다. 처리한 이벤트를 다시 다른 이벤트로 발행(다시 message 로 패키징해서)할 수도 있고 명령(commands)이 실행 될 수 있다.

 

brokers 와 messages 는 명령(commands)을 전달할 수도 있다. 이 경우, message 에는 실행할 action 에 대한 설명이 포함된다. 그리고 필요한 경우 다른 message 가 결과를 전달한다. 

Commands vs Events

ecommerce shop 을 예제로 command 와 event 차이에 대해서 알아보자. 사용자는 상품을 골라서 주문을 한다.

 

출처 : oreilly Reactive Systems in Java

 

사용자는 HTTP 요청을 사용해서 ShopService 에 명령(command) 을 보낸다. 전통적인 애플리케이션에서는 ShopService 가 명령을 받아 OrderService 의 주문 메서드를 호출한다. 여기서 주문 메서드를 호출하는 것은 명령(command) 이다. 이것은 ShopService 가 OrderService 를 의존하게 만든다. 그래서 OrderService 없이 ShopService 가 작동하지 못하게 되므로 컴포넌트 자율성을 떨어진다. 

 

이제 ShopService 와 OrderService 사이에서 명령(command) 대신 이벤트로 발행해보자. 사용자가 주문을 하면 애플리케이션이 ShopService 로 명령(command) 을 보내는것은 똑같다. 하지만 ShopService 는 명령을 이벤트(: a new order has been placed)로 바꾸고(transform) broker 로 보낸다. OrderService 는 'a new order has been placed' 이벤트를 consume 한다.

 

이 아키텍처에서는 ShopService 가 OrderService 를 의존하지 않는다(반대도 마찬가지). 

출처 : oreilly Reactive Systems in Java

 

그리고 위 그림을 보면 ShopService 에서 발행한 이벤트를 OrderService 뿐만 아니라 StatisticService 도 받는다. StatisticService 는 이벤트를 받고 가장 많이 주문된 아이템들을 추적한다. 즉 ShopService 수정 없이 같은 이벤트를 여러 컴포넌트들이 consume 할 수 있다.

 

또한 받은 이벤트를 갖고 다시 새롭게 발행할 수도 있다. 위 그림에서 StatisticService 는 주문 이벤트들을 분석해서 추천 상품들을 만들 수 있다. 추천 상품은 또 다른 fact 로 볼 수 있다. ShopService 는 추천 상품 event 를 consume 해서 관련 비지니스를 처리할 수 있다. 하지만 ShopService 와 StatisticService 는 서로 독립적이다. 지식(knowledge) 은 StatisticService 처럼 새로운 이벤트를 받아 다시 새로운 fact로 전하면서(발행) 축적되고 발생한다.

 

Orders, Recommendations 네이밍의 destination(topic 이나 queue) 은 이벤트를 순서대로 저장하는것이 중요하다. 순서를 유지함으로써 시스템을 과거 시간대로 돌려서 이벤트를 다시 처리하는것도 가능해진다. 장애가 발생했다면 다 해결 한 후 클린한 상태에서, 저장된 이벤트들을 다시 처리할 수 있다. 예를 들어 StatisticService 추천 알고리즘을 바꿨다면 모든 지식들을 다시 축적하고 새로운 추천으로 전달할 수 있게 된다.


message 를 사용하는 것은 시스템을 구성하는 컴포넌트들의 독립성(independence)을 증가시킨다. 이 독립성(independence) 은 상대적이다. 컴포넌트들은 서로를 모르지만 발행된 이벤트들과 receiver(consume 받는 애플리케이션) 들은 공통된 vocabulary 를 공유해야 한다. 이 vocabulary 는 종종 비지니스와 관련이 있지만 기술적인 것일 수도 있다. 

 

비지니스 이벤트(확실히 도메인 분석으로부터 나온다)인 경우, 명령과 이벤트 관점으로 시스템을 보면 구조(structure) 대신 행위(behavior) 에 focus 를 둘 수 있다. 행위(behavior) 에 focus 를 두면 communications 과 workflows 에 주의를 기울이게 된다. 그리고 이 분석을 통해 명령과 이벤트 중 어떤 interaction 이 적합한지, 어떤 데이터를 캡슐화해야 하는지, 어떻게 시스템 내 interaction 을 flow 시킬건지 결정할 수 있다. 


 

명령(commands) 과 이벤트(events)는 대부분의 interaction 의 기초다. 주로 명령을 사용하지만 이벤트는 큰 이득이 된다. 이벤트는 fact 다. 이벤트는 우리 시스템의 story 를 말해주고 시스템의 진화(evolution) 를 묘사하는 story 를 말해준다. reactive 시스템에서 이벤트는 message 안으로 wrap 된다. message 는 destination(topic 이나 queue) 으로 보내진다. 이러한 접근법은 분산 시스템에서 발생하는 중요한 구조적 문제를 해결한다. 첫째, real-world 비동기성을 자연스럽게 처리한다. 둘째, 강한 결합에 의존하지 않고 서비스들을  묶는다(bind). 또한 이 접근법은 시스템 edge 에서 HTTP 에 의존하면서 명령(commands) 을 사용한다.

출처 : oreilly Reactive Systems in Java

 

reactive 시스템의 이러한 비동기 message 전달 측면은 결합조직(connective tissue)을 형성한다. 이것은 시스템을 형성하는 애플리케이션들에게 더 많은 자율성과 독립성을 줄 수 있고 회복성(resilience) 과 탄력성(elasticity) 도 가능하게 한다.

 


마틴 파울러 EDA 강의

사람들 마다 말하는 event 가 약간씩 다르다. event 중심으로 만드는 전 세계 다양한 프로젝트들이 모두 동일한지는 확실하지 않다. 그래서 모여서 논의한 결과 도움이 되는 4가지 패턴을 이해하게 됐고 소개한다.

 

먼저 첫번째 패턴(Event Notification)을 시작해보자. 의존성 관점에서 얘기를 풀어나간다. 보험회사와 보험 시스템 예제다. 왼쪽 그림에서 고객이 주소를 변경했다. 주소 변경은 다른 자동차 보험이나 주택 보험에 영향을 미치는것이라 가정하자(requote : 인용하다 insurance quoting : 보험 견적).

 

여기서 Customer Management 시스템은 Insurance quoting 시스템으로 종속성을 생성한다. Insurance quoting 시스템이 거기 있다는것을 알아야 하고 어떤 API 가 있는지 알아야 하고 기타 등등 ... 그래서 Customer Management 시스템은 Insurance quoting 이 어떻게 동작하는지에 대한 지식 을 결합해야 한다. 이는 많은 사람들이 별로 좋아하지 않는 유형의 결합이다. 이 결합을 해결할 수 있는 한가지 방법은 event 발행을 하고 수신을 하게 바꿈으로써 직접적인 의존성을 끊는다. event 를 알림 시스템으로 사용하고 있다. 그래서 자기들끼리 토론에서 그렇게 이름 지었다.


마틴파울러는 이렇게 의존성을 역전시키는게 우리가 하고 싶은 이유의 본질이라고 생각한다.

 

Commands vs Events

이 둘의 차이는 미묘하고 엄격한 정의는 없지만 중요하다.
내가 이 사건의 결과로 무슨 일이 일어나는지 전혀 신경쓰지 않는다면(않고 싶다면) event 라는 단어를 사용한다.
반면에 내가 특별히 무언가가 일어나기를 바라는 것이 있으면 command 관점에서 단어를 사용한다.

 

원문 : https://learning.oreilly.com/library/view/reactive-systems-in/9781492091714/ 

참고 : 마틴파울러 EDA https://www.youtube.com/watch?v=STKCRSUsyP0 

참고 : 스프링캠프 2017 이벤트 소싱 소개

반응형

'Java' 카테고리의 다른 글

함수형 파라미터를 두번 전달하는 이슈 정리  (0) 2022.01.05
날짜 포맷 하위호환(기록용)  (0) 2022.01.04
slf4j error log 포맷  (0) 2021.11.17
The synchronous communication drawback(timing)  (0) 2021.11.12
Mono.defer  (0) 2021.11.11