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 자체의 물리적 튜닝: 브로커의 클러스터 복제 및 로그 관리 07-04-01 Message Brokers 영역으로 위임.
- Event Sourcing (이벤트 소싱): 이벤트를 DB처럼 사용하는 궁극의 패턴 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
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로 분리하는 리팩터링.
- Concepts: Subject (주체), Observer (관찰자), 1
- How to Learn:
- 1단계: 강결합의 고통:
OrderService안에EmailService,InventoryService객체를new로 만들어 호출합니다. 이메일 로직이 바뀌면 오더 코드를 수정해야 하는 개방-폐쇄 원칙(OCP) 위반을 해부합니다. - 2단계: 구독과 통보:
OrderService(Subject)는 자기를 쳐다보고 있는 리스트(List<Observer>)만 가집니다. 주문이 완료되면 리스트를 돌면서obs.update()만 호출합니다. 누가 리스트에 들어있는지OrderService는 몰라도 되는 우아한 분리를 뜯어봅니다.
- 1단계: 강결합의 고통:
- Implement: 파이썬 Observer 패턴 구현.
YouTubeChannel클래스에subscribe(user)로 관찰자를 등록하고,upload_video()호출 시 등록된 모든 유저 객체의notify()메서드를 루프 돌면서 호출(print)하는 기본 패턴 렌더링.
Recommended
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줄도 수정되지 않는 확장성의 극치를 해부합니다.
- 1단계: 명령(Command):
- Implement: 이벤트 명명 규칙(Naming Convention) 및 페이로드 스키마 설계.
UserAccountCreated { userId, timestamp, email }와 같은 과거시제(Past Tense) 이벤트 JSON 구조체를 설계하고, 이 이벤트를 받아 각기 다른 행동을 하는EmailService와AnalyticsService더미 함수 매핑 콘솔 렌더링.
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 B 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
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) 방식이 도입되었을 때, 전체 비즈니스 흐름(예: 주문 재고 결제)을 한눈에 모니터링하기 어려워지는 단점을 해부할 수 있는가?
- 이벤트를 설계할 때
UpdateUser대신UserUpdated와 같이 과거시제(Past Tense) 명명 규칙을 엄격하게 적용하여 '불변의 사실(Fact)'임을 시스템 전체에 각인시키는 도메인 설계 철학을 평가할 수 있는가?
Industry
- 수십 개의 서비스가 얽힌 이벤트 흐름에서 장애가 터졌을 때, 최초 발생지부터 끝단까지의 로그를 한 줄로 꿰어내기 위해
Correlation ID (Trace ID)를 HTTP 헤더와 카프카 메시지에 물고 다니게(Propagation) 하는 분산 추적망을 설계할 수 있는가? - 카프카를 통해 JSON 이벤트를 쏠 때, 필드 이름 변경이나 타입 변경(Int String)으로 인한 하위 호환성(Backward Compatibility) 붕괴를 막기 위해 Confluent Schema Registry와 Avro 포맷을 어떻게 방어선으로 구축할지 아키텍처 관점으로 제시할 수 있는가?