콘텐츠로 바로가기

Pub-Sub, Observer & Event-driven Flows

일대다 통신을 위한 메시지 배포 모델과, 시스템의 상태 변화를 기점으로 연쇄적인 반응을 끌어내는 이벤트 중심 워크플로우를 다루는 학습 노드입니다.

Article
M

Me

hyunyoun's Blog

system-architecture-distributed-systemssystem-architecturedistributed-systemsevent-drivenreactive-systemspub-subobserverevent-driven-flows9 min read

1. Overview

Pub-Sub, Observer 패턴, 그리고 이벤트 기반 흐름(Pub-Sub, Observer & Event-driven Flows)은 객체와 마이크로서비스 간의 단단한 사슬(의존성)을 끊어내고, 오직 "이벤트(사건)"를 통해서만 소통하는 비동기적이고 탈중앙화된 아키텍처의 신경망을 해부합니다.

학습자는 단일 프로세스 내에서 객체 간의 결합도를 낮추는 Gof 디자인 패턴의 Observer 패턴을 이해하고, 이를 네트워크 브로커(Broker)를 통해 분산 시스템 전체로 확장한 Pub/Sub(발행-구독) 모델의 철학적 차이를 뜯어봅니다. 나아가 "사용자가 생성되었다(UserCreated)"라는 과거의 불변하는 사실(Event)을 기반으로 수많은 서비스가 각자 알아서 행동하게 만드는 **이벤트 주도 아키텍처(Event-Driven Architecture, EDA)**의 우아함을 장악합니다. 마지막으로, 중앙 통제(Orchestration) 없이 각 서비스가 스스로 춤을 추듯 엮이는 Choreography 흐름 속에서 이벤트 스키마(Schema)와 디버깅 지옥을 뚫어내는 설계 역량을 확보합니다.

2. Scope & Boundaries

In-Scope

  • Observer Pattern: 주체(Subject)와 관찰자(Observer) 간의 강한 결합(Interface)과 동기적 호출.
  • Pub/Sub Pattern: 발행자(Publisher)와 구독자(Subscriber) 사이에 브로커(Broker)를 둔 완벽한 결합 분리.
  • Event-Driven Architecture (EDA): 상태 변경(State Change)을 나타내는 불변의 도메인 이벤트(Domain Event) 흐름.
  • Event Schema Evolution: 이벤트 페이로드(JSON/Avro)의 버전 관리와 스키마 레지스트리(Schema Registry).

Out-of-Scope

  • Kafka, RabbitMQ 자체의 물리적 튜닝: 브로커의 클러스터 복제 및 로그 관리 \rightarrow 07-04-01 Message Brokers 영역으로 위임.
  • Event Sourcing (이벤트 소싱): 이벤트를 DB처럼 사용하는 궁극의 패턴 \rightarrow 07-04-04 Event Sourcing & CQRS 영역.

Boundaries

  • Command vs Event: REST API로 날리는 CreateUser()나 큐에 넣는 SendEmailCommand는 수신자가 누구인지 명확히 알고 "명령(Command)"을 내리는 행위입니다(결합도 높음). 반면 이벤트 주도 아키텍처에서 발행하는 UserCreatedEvent는 "유저가 방금 생성됐어. 난 누가 이 소식을 들을지 관심 없고 난 내 할 일 끝났어"라는 "과거의 사실 통보(Event)"입니다. EDA는 시스템을 극도로 유연하게(확장성 100%) 만들지만, 흐름이 파편화되어 "도대체 왜 이메일이 안 나간 거지?" 하고 추적(Tracing)하기가 끔찍하게 어려워지는 트레이드오프를 명확히 경계 짓습니다.

3. Counterexample

  • 동기 Observer의 함정: 결제 클래스(Payment)에 5개의 Observer(이메일 발송, 재고 차감, 포인트 적립 등)가 등록되어 있습니다. 결제가 완료되자 notifyObservers()를 호출하여 동기적으로 5개의 함수를 순차 실행합니다. 이때 이메일 발송 외부 API가 타임아웃 10초를 발생시키면, 나머지 재고와 포인트 로직도 멈춰버리고 최종적으로 결제 처리 자체가 실패해버리는, 결합은 느슨해졌으나(Interface) 실행 시간은 결합되어(Synchronous) 있는 무늬만 느슨한 결합입니다. (대안: 비동기 Pub/Sub 큐 도입).
  • 이벤트 스키마(Schema) 붕괴: A 서비스가 "회원가입" 이벤트를 JSON으로 Kafka에 쏩니다. B 서비스가 이를 가져다 씁니다. 어느 날 A 서비스 개발자가 JSON 키를 user_name에서 username으로 살짝 바꿨습니다. A 서비스는 에러 없이 배포되었지만, B 서비스는 바뀐 키를 파싱하지 못해 조용히 뻗어버렸고 3일 동안 신규 회원의 웰컴 이메일이 한 건도 나가지 않았습니다. 브로커를 맹신하여 이벤트 스키마(계약) 버전 관리를 간과한 최악의 안티 패턴입니다.

4. Prerequisites

  • 객체지향 설계 기초 (Basic): 인터페이스와 의존성 역전(DIP). (05-01-01 OOP Patterns)
  • 메시지 큐 통신 (Basic): Producer와 Consumer 개념. (07-04-01 Message Brokers)

5. Learning Map

Sequence Core Cluster Objective & Description Evidence (BoK)
1 Observer Pattern "내 상태가 변하면 너희에게 알려줄게"라는 인터페이스 기반의 1 알림 구조를 통해 객체 간 결합도를 낮추는 첫 단추를 쥡니다. P1
2 Pub-Sub Pattern Observer의 한계를 넘어, 브로커(Broker)를 중간에 세워 서로의 존재조차 알지 못하는 100% 결합 분리의 마법을 해부합니다. P5
3 Event-Driven Architecture "명령(Do this)"이 아닌 "사건(This happened)"의 흐름으로 마이크로서비스 전체를 자율적인 군집(Choreography)으로 춤추게 만듭니다. Industry
4 Schema Registry & Tracing 이벤트 흐름이 거미줄처럼 얽힐 때 발생하는 스키마 붕괴(JSON 변경)와 추적 불가 지옥을 통제하는 인프라 역량을 장악합니다. Industry

6. Learning Topics

Basic

Core Topic 01: 알림의 탄생, Observer 패턴 (Observer Pattern)

  • Why to Learn: A 객체 안에 B.doSomething()을 하드코딩하는 끔찍한 의존성을 인터페이스(Interface)를 통해 느슨하게 풀어내는 디자인 패턴의 근본을 장악하기 위함입니다.
  • What to Learn:
    • Concepts: Subject (주체), Observer (관찰자), 1 관계, Event Listener, 동기적(Synchronous) 호출의 한계.
    • Skills: 도메인 로직과 부가 로직(이메일 발송, 로깅 등)을 Observer로 분리하는 리팩터링.
  • How to Learn:
    • 1단계: 강결합의 고통: OrderService 안에 EmailService, InventoryService 객체를 new로 만들어 호출합니다. 이메일 로직이 바뀌면 오더 코드를 수정해야 하는 개방-폐쇄 원칙(OCP) 위반을 해부합니다.
    • 2단계: 구독과 통보: OrderService(Subject)는 자기를 쳐다보고 있는 리스트(List<Observer>)만 가집니다. 주문이 완료되면 리스트를 돌면서 obs.update()만 호출합니다. 누가 리스트에 들어있는지 OrderService는 몰라도 되는 우아한 분리를 뜯어봅니다.
  • Implement: 파이썬 Observer 패턴 구현. YouTubeChannel 클래스에 subscribe(user)로 관찰자를 등록하고, upload_video() 호출 시 등록된 모든 유저 객체의 notify() 메서드를 루프 돌면서 호출(print)하는 기본 패턴 렌더링.

Core Topic 02: 서로를 모르는 쾌감, Pub-Sub 패턴 (Pub-Sub Pattern)

  • Why to Learn: 메모리(RAM) 위에서 동작하는 Observer 패턴을 서버 간의 네트워크 세계로 확장하여, 발행자와 구독자가 완전히 분리된 클라우드 통신망을 세우기 위함입니다.
  • What to Learn:
    • Concepts: Publisher(발행자), Subscriber(구독자), Message Broker / Event Bus, Topic (주제/채널), Fire and Forget.
    • Skills: 메시지 브로커(Redis Pub/Sub, Kafka)를 활용한 Topic 기반 비동기 라우팅 설계.
  • How to Learn:
    • 1단계: 완벽한 남남(Decoupling): Observer 패턴은 여전히 주체(Subject)가 관찰자(Observer)의 메모리 주소를 들고(List) 있어야 합니다. 하지만 Pub-Sub은 중간에 '우체국(Broker)'을 둡니다. Publisher는 "이 편지를 우체국에 던질게" 하고 사라지고, Subscriber는 "우체국에 새 편지 오면 나 줘"라고만 하는 완벽한 시공간의 분리를 해부합니다.
    • 2단계: 토픽(Topic) 채널: 편지를 보낼 때 봉투에 '결제(Topic)', '가입(Topic)'이라고 적습니다. 구독자는 자기가 관심 있는 봉투(Topic)만 구독하여 수백만 개의 메시지 중 필요한 것만 골라 먹는 라우팅 기전을 뜯어봅니다.
  • Implement: Pub-Sub 중개자(Broker) 시뮬레이션. Broker 클래스에 publish(topic, msg)subscribe(topic, callback) 구현. Producer 스레드가 broker.publish('sports', 'Goal!')을 던지면, 0.1초 뒤 sports 토픽을 구독한 2개의 Consumer 스레드가 비동기로 메시지를 콜백(Callback)받아 출력하는 코드 렌더링.

Practical

Core Topic 03: 명령과 사건의 차이, 이벤트 주도 아키텍처 (Event-Driven Architecture)

  • Why to Learn: "명령(Command)"을 통한 강제적 통제에서 벗어나, 각 마이크로서비스가 "도메인 이벤트(Domain Event)"에 반응하여 스스로 동작하는 궁극의 확장성을 장악하기 위함입니다.
  • What to Learn:
    • Concepts: Command vs Event, Event-Driven Architecture (EDA), Choreography(자율 릴레이), Domain Event (과거시제 명명 규칙).
    • Skills: 비즈니스 흐름을 중앙 통제 API(Orchestration)에서 비동기 이벤트 연쇄(Choreography) 흐름으로 재설계.
  • How to Learn:
    • 1단계: 명령(Command): SendEmailCommand. 명령은 "누가 이 일을 해야 하는지" 보낸 사람이 정확히 아는 것입니다. 시스템에 새 서비스(예: 통계팀)가 추가되면, 보내는 쪽의 코드를 수정해 "통계 API도 찔러줘"라고 추가해야 합니다(결합).
    • 2단계: 이벤트(Event): UserRegisteredEvent. 이벤트는 "방금 이런 일이 생겼어"라는 통보입니다. 새 통계 서비스가 생기면, 걔가 알아서 카프카에 접속해 UserRegistered 이벤트를 주워 듣기만 하면 됩니다. 발행자(User 서비스)의 코드는 단 1줄도 수정되지 않는 확장성의 극치를 해부합니다.
  • Implement: 이벤트 명명 규칙(Naming Convention) 및 페이로드 스키마 설계. UserAccountCreated { userId, timestamp, email }와 같은 과거시제(Past Tense) 이벤트 JSON 구조체를 설계하고, 이 이벤트를 받아 각기 다른 행동을 하는 EmailServiceAnalyticsService 더미 함수 매핑 콘솔 렌더링.

Advanced

Core Topic 04: 자유의 대가, 스키마 레지스트리와 추적 (Schema Registry & Tracing)

  • Why to Learn: 각 서비스가 중앙 통제 없이 맘대로 이벤트를 던지는 환경에서 필연적으로 발생하는 "데이터 포맷 붕괴"와 "디버깅 불가 지옥"을 통제하는 인프라 뼈대를 세우기 위함입니다.
  • What to Learn:
    • Concepts: Schema Registry (스키마 저장소), Avro / Protobuf, Backward Compatibility(하위 호환성), Distributed Tracing (분산 추적 - TraceID), Correlation ID.
    • Skills: 중앙 집중형 Schema Registry를 통한 발행/구독 간의 데이터 계약(Contract) 강제 및 TraceID 전파(Propagation).
  • How to Learn:
    • 1단계: 스키마 붕괴 방어: JSON 필드 이름을 맘대로 바꿔서 전체 시스템이 연쇄 크래시 나는 것을 막기 위해, Kafka Schema Registry를 도입합니다. 발행자가 메시지를 쏠 때 레지스트리에 "이 스키마 버전 맞아?"라고 먼저 검사받아, 하위 호환성(Backward Compatibility)이 깨진 메시지는 발송 자체를 차단하는 룰을 해부합니다.
    • 2단계: 미아 방지(Trace ID): A \rightarrow B \rightarrow C로 이벤트가 흘러가다 C에서 터졌습니다. 로그를 뜯어봐도 이게 누구 요청인지 알 수가 없습니다. A에서 이벤트를 쏠 때 HTTP 헤더나 이벤트 메타데이터에 TraceID(Correlation ID)를 찍어 보내고, B와 C가 그 ID를 고스란히 물고 다니게 만들어 전체 비동기 흐름을 한 줄로 꿰어내는 추적망(Jaeger/Zipkin)을 뜯어봅니다.
  • Implement: Correlation ID (Trace ID) 전파 릴레이. Event_A { trace_id: "tx-999", data: ... }를 발송. 수신한 Service_B가 로그에 [tx-999] Processing...을 찍고, 후속 Event_B를 던질 때 똑같이 trace_id: "tx-999"를 박아 넣음. 나중에 로그 파일 3개를 합쳐 grep "tx-999" 명령어로 하나의 완벽한 트랜잭션 타임라인을 뽑아내는 텍스트 파싱 데모.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
Observer Pattern 하나의 객체(주체) 상태가 변할 때, 그 객체에 등록된 리스트를 순회하며 여러 관찰자 객체들의 메서드를 쫘르륵 동기적으로 호출해 주는 디자인 패턴입니다. 기본 클래스 간 결합도 완화 GoF Patterns Pub/Sub Pattern 호출 자체는 여전히 동기(Sync)이므로, 관찰자 중 하나가 무한 루프에 빠지면 주체도 영원히 멈춤 P1:CS2023 core
Pub-Sub Pattern 발행자(Publisher)와 구독자(Subscriber) 사이에 메시지 브로커를 두어 서로의 존재를 100% 모른 채(시공간의 분리) '토픽(Topic)' 기반으로 통신하는 아키텍처 패턴입니다. 권장 서비스 간 완전한 분리 Message Broker (Kafka) Observer Pattern 발행자는 누가 내 메시지를 읽는지, 심지어 읽는 애가 죽었는지 살았는지도 모름(Fire and Forget) P5:SFIA core
Event-Driven Architecture 서비스 간에 API로 '명령'을 내리는 대신, "주문이 완료됨" 같은 과거시제의 불변 이벤트(Event)를 큐에 뿌리고 관심 있는 서비스가 자율적으로 반응하게 만드는 패러다임입니다. 실무 MSA 궁극의 확장성 Domain Event Request-Driven (동기) 유연성은 미친 듯이 올라가지만, 이벤트가 어디로 흘러가는지 한눈에 파악하기 힘든 복잡도의 늪에 빠짐 Industry core
Schema Registry 브로커를 통해 이벤트를 주고받을 때, 발행자가 몰래 필드 이름을 바꾸거나 데이터 타입을 변경하여 구독자가 크래시 나는 것을 막아주는 중앙 스키마(Avro 등) 검증 저장소입니다. 심화 이벤트 포맷 붕괴 방어 Apache Avro / Protobuf Backward Compatibility 스키마를 강제하지 않는 JSON 통신은 프로젝트가 커질수록 연쇄 장애 폭탄이 됨 Industry core

8. References

Primary

  • [P1] CS2023 - Software Development Fundamentals (SDF) - Design Patterns (Observer)
  • [P5] SFIA - Enterprise IT Architecture (ARCH) - Event-driven Architecture

Secondary

  • [Design Patterns: Elements of Reusable Object-Oriented Software] GoF - Observer Pattern
  • [Building Microservices] Sam Newman - Event-Driven Architecture and Choreography

Industry

  • [Confluent Documentation] - Kafka Schema Registry and Schema Evolution
  • [MartinFowler.com] - What do you mean by "Event-Driven"?

9. Final Checklist

Primary

  • 단일 프로세스 내의 Observer 패턴이 갖는 '동기적 실행(Synchronous Call)'의 한계를 설명하고, 이를 해결하기 위해 물리적 브로커를 도입한 Pub-Sub 패턴의 시공간 분리 역학을 비교할 수 있는가?
  • 마이크로서비스 간 통신에서 "나의 상태를 변경해 줘"라는 명령(Command) 기반 API 호출과, "나의 상태가 방금 변경되었어"라는 이벤트(Event) 발행 방식의 확장성(Scalability) 차이를 논증할 수 있는가?

Secondary

  • 이벤트 주도 아키텍처(EDA)에서 중앙 통제자 없이 자율적으로 이벤트를 릴레이하는 코레오그래피(Choreography) 방식이 도입되었을 때, 전체 비즈니스 흐름(예: 주문 \rightarrow 재고 \rightarrow 결제)을 한눈에 모니터링하기 어려워지는 단점을 해부할 수 있는가?
  • 이벤트를 설계할 때 UpdateUser 대신 UserUpdated와 같이 과거시제(Past Tense) 명명 규칙을 엄격하게 적용하여 '불변의 사실(Fact)'임을 시스템 전체에 각인시키는 도메인 설계 철학을 평가할 수 있는가?

Industry

  • 수십 개의 서비스가 얽힌 이벤트 흐름에서 장애가 터졌을 때, 최초 발생지부터 끝단까지의 로그를 한 줄로 꿰어내기 위해 Correlation ID (Trace ID)를 HTTP 헤더와 카프카 메시지에 물고 다니게(Propagation) 하는 분산 추적망을 설계할 수 있는가?
  • 카프카를 통해 JSON 이벤트를 쏠 때, 필드 이름 변경이나 타입 변경(Int \rightarrow String)으로 인한 하위 호환성(Backward Compatibility) 붕괴를 막기 위해 Confluent Schema Registry와 Avro 포맷을 어떻게 방어선으로 구축할지 아키텍처 관점으로 제시할 수 있는가?

System Architecture · Event-Driven & CQRS

2 / 7