Day 2. EDA 기초
1. 오늘의 학습 목표
- EDA와 이벤트가 필요한 이유를 설명할 수 있다.
- 동기 통신과 비동기 통신의 차이를 주문 흐름에 적용할 수 있다.
- Producer, Broker, Consumer의 역할을 구분할 수 있다.
- Command와 Event를 구분하고 적절한 이름을 지을 수 있다.
- 느슨한 결합과 최종적 일관성의 기초를 설명할 수 있다.
2. 학습 전 생각해보기
질문 1
Order 서비스가 Stock과 Payment를 순서대로 직접 호출하면 어떤 정보를 알아야 할까?
내 답변
따로 알필요 없이 Order 서비스가 자신의 상태를 브로커를 통해 전달하고, 각각 다른 서비스 들이 해당 정보를 polling하여 받은뒤 각자가 알아서 해야할 일을한다.
질문 2
주문 API가 “접수됨”을 응답했지만 결제가 2초 뒤 완료되는 상태는 잘못된 것일까?
내 답변
잘못된 것이 아니다. 비동기 방식으로 처리가 이루어지므로, 2초뒤에 완료되는 상태는 정상이다.
질문 3
CreateOrder와 OrderCreated라는 이름에서 각각 요청과 사실 중 무엇이 느껴지는가?
내 답변
CreateOrder는 주문 이라는 것을 생성(요청)하는 느낌이고, OrderCreated은 생성한 주문이 준비되었다(사실)는 느낌이다.
3. 핵심 개념 설명
3.1 EDA와 이벤트
EDA(Event-Driven Architecture)는 시스템에서 일어난 사건을 이벤트로 알리고, 관심 있는 구성 요소가 이를 받아 후속 작업을 수행하는 구조다. 이벤트는 “주문이 생성되었다”처럼 이미 일어난 업무 사실을 나타낸다.
서비스가 분리되면 한 서비스의 변화가 다른 서비스의 작업을 시작하게 해야 한다. Order가 OrderCreated를 발행하면 Stock은 재고를 차감하고, 분석 서비스가 있다면 주문 통계를 갱신할 수 있다. Order는 모든 후속 서비스의 내부 구현을 알 필요가 없다.
이벤트가 모든 통신을 대체하는 것은 아니다. 즉시 결과가 필요한 조회나 사용자의 명령 접수에는 동기 API가 더 자연스러울 수 있다. EDA는 사건에 반응하는 흐름과 서비스 간 결합을 줄이는 데 특히 유용하다.
3.2 동기 통신과 비동기 통신
동기 통신에서는 요청을 보낸 쪽이 응답을 기다린다. Order가 Stock API를 호출했다면 Stock의 성공이나 실패가 돌아올 때까지 다음 처리를 기다린다. 흐름을 따라가기 쉽지만 Stock이 느리거나 중단되면 Order 요청도 영향을 받는다.
비동기 통신에서는 보내는 쪽이 이벤트를 전달한 뒤 소비자의 처리가 끝날 때까지 같은 호출에서 기다리지 않는다. Order가 이벤트를 발행하고 응답한 후 Stock이 나중에 처리할 수 있다. 시간적 결합이 줄어드는 대신 처리 결과가 즉시 확정되지 않는다.
둘 중 하나가 항상 우월하지 않다. “지금 결과가 꼭 필요한가”, “상대 장애를 기다릴 수 있는가”, “잠시 불일치해도 되는가”를 기준으로 선택한다.
3.3 Producer, Broker, Consumer
Producer는 이벤트를 발행하는 주체다. 주문 생성 사실을 알리는 Order 서비스가 Producer가 된다. Consumer는 이벤트를 받아 자기 책임을 수행하는 주체이며, OrderCreated를 받아 재고를 차감하는 Stock 서비스가 해당한다.
Broker는 Producer와 Consumer 사이에서 메시지를 받아 보관하고 전달을 중개한다. 이 프로젝트에서는 Kafka가 Broker 역할을 맡는다. Producer는 Consumer의 주소나 실행 시점을 직접 관리하지 않고 Broker에 기록한다.
이 역할 분리는 서비스 간 직접 의존을 줄인다. 단, 메시지를 어떻게 저장하고 재전달하는지는 중요한 주제다.
이후 학습: Kafka의 Topic, Partition, Offset 등 Broker 내부 구조는 Day 3에서 다룬다.
3.4 Command와 Event
Command는 누군가에게 행동을 요청한다. ReserveStock은 “재고를 확보하라”는 의도를 담고 있으며 성공 여부가 아직 정해지지 않았다. 보통 명령을 수행할 책임이 있는 수신자가 분명하다.
Event는 이미 발생한 사실을 알린다. OrderCreated는 주문 생성이 완료되었다는 사실이며, 이를 받은 소비자는 사실 자체를 취소할 수 없다. 여러 소비자가 각자의 반응을 할 수 있다.
이벤트 이름을 과거형으로 쓰는 이유는 요청과 완료된 사실을 구분하기 위해서다. CreateOrder라고 쓰면 실행해야 할 명령인지 이미 생성되었다는 알림인지 모호하다. 과거형은 “무엇이 일어났는가”를 드러낸다.
3.5 느슨한 결합과 최종적 일관성
느슨한 결합은 한 구성 요소의 변경이 다른 구성 요소에 미치는 영향을 줄인 상태다. Order가 OrderCreated라는 약속만 지키면 Stock의 내부 클래스나 DB 구조를 알 필요가 없다. 새로운 소비자가 추가되어도 Producer를 반드시 수정할 필요는 없다.
비동기 처리에는 시간 차이가 있다. 주문은 생성됐지만 재고 차감 결과가 아직 반영되지 않은 순간이 생긴다. 시간이 지나 이벤트 처리가 완료된 뒤 시스템 전체가 일관된 상태에 도달하는 성질을 최종적 일관성이라고 한다.
최종적 일관성은 아무 상태나 허용한다는 뜻이 아니다. 중간 상태를 업무적으로 정의하고, 결국 올바른 상태에 도달하도록 실패를 처리해야 한다.
이후 학습: 중복, 재시도, 처리 실패는 Day 4에서, 여러 서비스의 실패를 복구하는 방법은 Day 6에서 다룬다.
4. 흐름으로 이해하기
Client ──동기 요청──→ Order Service
│
│ OrderCreated 발행 (Producer)
↓
Broker
│
비동기 전달 │
↓
Stock Service (Consumer)
│
└── 재고 차감
사용자는 Order에 주문 생성을 직접 요청하고 응답을 기다린다. Order는 발생한 사실을 Broker에 알린다. Stock은 그 이벤트를 자기 시점에 받아 재고 책임을 수행한다.
5. 직접 채우는 핵심 질문
질문 1. EDA를 “이미 일어난 사실”과 “후속 반응”이라는 표현으로 설명하라.
내 답변
Event(사실), Command(명령)?
핵심 질문 정석 답변 보기
질문 1. EDA를 “이미 일어난 사실”과 “후속 반응”이라는 표현으로 설명하라. 정석 답변 EDA는 시스템에서 이미 일어난 사실을 이벤트로 발행하고, 그 사실에 관심이 있는 Consumer가 이벤트를 받아 후속 작업을 수행하는 구조다. 예를 들어 Order 서비스가 주문을 생성한 뒤 OrderCreated 이벤트를 발행하면, Stock 서비스는 이를 소비하여 재고 차감 작업을 수행할 수 있다. 이때 Order 서비스는 Stock 서비스가 구체적으로 어떤 방식으로 재고를 처리하는지 알 필요가 없다. Order는 주문이 생성되었다는 사실만 알리고, Consumer가 자신의 책임에 맞게 반응한다.답변에 포함해야 할 키워드
- 이벤트
- 사실
- 소비자
질문 2. Order가 Stock API를 동기로 호출할 때 생기는 시간적 결합은 무엇인가?
내 답변
Order 서비스가 Stock API로 부터 응답이 올때까지 동기적으로, 응답 대기의 형태로 작동하므로, 그 트래픽의 라이프사이클만큼의 리소스를 낭비하게된다. 이게 두개가 시간적으로 결합되어있다는 뜻 아닐까 ..?
핵심 질문 정석 답변 보기
질문 2. Order가 Stock API를 동기로 호출할 때 생기는 시간적 결합은 무엇인가? 정석 답변 Order가 Stock API를 동기로 호출하면 Stock의 응답이 돌아올 때까지 Order의 처리를 완료할 수 없다. 즉, 두 서비스가 같은 시간에 정상적으로 실행되고 있어야 주문 처리가 진행된다. 이것을 시간적 결합이라고 한다. Stock의 응답이 느려지면 Order의 응답도 함께 느려진다. Stock에 장애가 발생하거나 응답 시간이 초과되면 Order의 주문 처리도 실패할 수 있다. 따라서 Order의 처리 시간과 성공 여부가 Stock의 응답 시점과 상태에 직접 영향을 받는다. 참고로 이것은 단순히 리소스가 낭비된다는 의미보다, 호출하는 서비스가 상대 서비스의 응답 시점과 실행 가능 여부에 의존한다는 의미가 더 중요하다.답변에 포함해야 할 키워드
- 응답 대기
- 지연
- 장애 영향
질문 3. 비동기 방식이 주문 결과를 즉시 확정하기 어렵게 만드는 이유는 무엇인가?
내 답변
DB에 정보를 저장하고, 브로커에게 이벤트를 보내는데 이벤트 보내는게 실패하면, 서로 상태가 엇갈리는? 그런 상황이되서 확정하기 위해서는 안전장치들이 필요하다
핵심 질문 정석 답변 보기
질문 3. 비동기 방식이 주문 결과를 즉시 확정하기 어렵게 만드는 이유는 무엇인가? 정석 답변 비동기 방식에서는 Order가 이벤트를 발행한 시점과 Stock이나 Payment가 이벤트를 처리하는 시점이 서로 다르다. 주문이 생성되었더라도 Stock이 아직 이벤트를 처리하지 않았다면 재고 차감 결과를 알 수 없다. 재고 차감이 완료되었더라도 Payment가 아직 처리 중일 수 있다. 따라서 주문 요청을 받은 순간에는 전체 결과를 즉시 확정하기 어렵다. 이 과정에서 Order는 CREATED, STOCK_RESERVED, PAYMENT_PENDING과 같은 중간 상태를 가질 수 있다. 이후 각 Consumer의 처리가 성공하거나 실패한 결과를 반영하면서 주문은 COMPLETED 또는 CANCELED 같은 올바른 최종 상태에 도달한다. 이를 최종적 일관성이라고 한다. DB 저장 성공과 이벤트 발행 실패 문제도 중요하지만, 이 질문의 중심은 비동기 Consumer들의 처리 시점이 서로 다르다는 것이다. DB 저장과 이벤트 발행 사이의 문제는 Transactional Outbox와 관련된 별도의 신뢰성 문제다.답변에 포함해야 할 키워드
- 처리 시점
- 중간 상태
- 최종적 일관성
질문 4. PaymentRequested와 RequestPayment 중 Event와 Command에 더 어울리는 이름을 각각 고르고 이유를 적어라.
내 답변
과거형이 Event과 현재형이 Command이다. Event는 일어난 사실을 말하는 말이고, Command는 어떤일을 실행시킬때 사용하는 말이다.
핵심 질문 정석 답변 보기
질문 4. PaymentRequested와 RequestPayment 중 Event와 Command에 더 어울리는 이름을 각각 고르고 이유를 적어라. 정석 답변 PaymentRequested는 Event에 더 어울린다. 결제가 요청되었다는 이미 발생한 사실을 과거형으로 표현하기 때문이다. RequestPayment는 Command에 더 어울린다. 결제를 요청하라는 행동을 특정 대상에게 지시하는 이름이기 때문이다. Event는 이미 발생한 사실을 표현하므로 일반적으로 과거형으로 이름을 작성한다. Command는 아직 수행되지 않은 행동을 요청하므로 RequestPayment, DeductStock처럼 명령의 형태로 작성한다.답변에 포함해야 할 키워드
- 과거형
- 요청
- 사실
질문 5. Producer가 Consumer의 주소를 직접 몰라도 되는 이유는 무엇인가?
내 답변
EDA기 떄문에 각자는 각자의 상태를 알려주고 브로커를 통해 상대의 상태를 받아 나의 다음 작업을 진행하면 된다.
핵심 질문 정석 답변 보기
질문 5. Producer가 Consumer의 주소를 직접 몰라도 되는 이유는 무엇인가? 정석 답변 Producer는 Consumer에게 직접 이벤트를 전송하지 않고 Broker의 Topic에 이벤트를 발행한다. Consumer는 자신이 관심 있는 Topic을 구독하고 Broker로부터 이벤트를 가져간다. 따라서 Producer는 Consumer가 몇 개 존재하는지, 어디에서 실행되는지, 어떤 주소를 사용하는지 직접 알 필요가 없다. Broker가 Producer와 Consumer 사이를 중개하기 때문에 두 서비스의 직접적인 결합이 줄어든다. 이를 느슨한 결합이라고 한다. 다만 Producer와 Consumer가 완전히 아무 관계도 없는 것은 아니다. Topic과 이벤트 데이터 형식 같은 이벤트 계약은 서로 맞아야 한다.답변에 포함해야 할 키워드
- Broker
- 중개
- 느슨한 결합
질문 6. OrderCreated에 이메일 서비스가 새 Consumer로 추가되면 Order 코드를 꼭 바꿔야 하는가?
내 답변
Order는 따로 바뀌지 않는다. 내부 규칙과 외부 인터페이스를 잘 정의해놨다면, 아 그래도 뭐라고 딱 잘라 말하긴 어렵네 따로 꼭 바뀔 필요는 없다 .
핵심 질문 정석 답변 보기
질문 6. OrderCreated에 이메일 서비스가 새 Consumer로 추가되면 Order 코드를 꼭 바꿔야 하는가? 정석 답변 일반적으로 Order 코드를 변경할 필요는 없다. Order는 기존과 동일하게 OrderCreated 이벤트를 Broker에 발행한다. 새 이메일 서비스가 해당 Topic을 구독하여 OrderCreated 이벤트를 소비하고 이메일을 보내면 된다. Producer인 Order는 새로운 Consumer의 주소나 내부 처리 방법을 알 필요가 없다. 따라서 Consumer가 추가되어도 Producer와의 직접적인 결합이 증가하지 않는다. 단, 이메일 서비스에 필요한 정보가 기존 OrderCreated 이벤트에 없어서 이벤트 계약 자체를 변경해야 한다면 Order 코드의 변경이 필요할 수 있다. 하지만 기존 이벤트 정보만으로 처리할 수 있다면 Order는 변경하지 않아도 된다.답변에 포함해야 할 키워드
- 구독
- Producer
- 결합
질문 7. 즉시 상품 상세 정보를 보여주는 조회에도 무조건 이벤트를 사용해야 한다는 주장에 반박하라.
내 답변
DB에 insert 나 update 와 같은 쓰기 작업이 아니기 때문에 음 .. command를 통해서 조회는 가능하지 않을까? 싶긴하다. 근데 만약 해당 서비스가 사용량이 과도하거나, 사용에 제한이 되는 상황을 알리는 이벤트같은 경우에는 이벤트가 쓰일수도 있지 않을까?
핵심 질문 정석 답변 보기
질문 7. 즉시 상품 상세 정보를 보여주는 조회에도 무조건 이벤트를 사용해야 한다는 주장에 반박하라. 정석 답변 사용자가 상품 상세 정보를 요청하고 즉시 결과를 확인해야 한다면 동기 HTTP API가 더 적절할 수 있다. 이벤트는 Producer가 결과를 즉시 기다리지 않아도 되는 후속 작업이나, 이미 발생한 사실을 여러 Consumer에게 알리는 상황에 적합하다. 반면 상품 상세 조회는 요청한 사용자가 상품명, 가격, 판매 상태 등의 결과를 바로 받아야 한다. 따라서 Product 서비스에 동기 API로 조회를 요청하고 응답을 받는 방식이 목적에 더 잘 맞는다. EDA를 사용한다고 해서 모든 통신을 이벤트로 처리해야 하는 것은 아니다. 통신의 목적과 즉시 결과가 필요한지를 기준으로 동기와 비동기를 선택해야 한다. 참고로 조회는 Command가 아니라 Query라고 구분하는 것이 더 정확하다. Command는 상태 변경을 요청하고, Query는 데이터를 조회한다.답변에 포함해야 할 키워드
- 즉시 결과
- 동기 통신
- 목적
질문 8. 최종적 일관성을 “언젠가 알아서 맞는다”라고만 설명하면 부족한 이유는 무엇인가?
내 답변
흠.. 언젠간 알아서 맞는다는게 무슨 말이지.. 결국에 보상트랜잭션이나 OutBox등을 활용하면 최종적으로 롤백했을때의 일관성과 사가 방식의 일관성이 일치한다는 뜻 아닐까 ..
핵심 질문 정석 답변 보기
질문 8. 최종적 일관성을 “언젠가 알아서 맞는다”라고만 설명하면 부족한 이유는 무엇인가? 정석 답변 최종적 일관성은 아무런 처리 없이 시간이 지나면 데이터가 자동으로 맞는다는 뜻이 아니다. 비동기 처리 중에는 Order가 CREATED이고 Stock은 아직 처리하지 않은 것처럼 서비스별 상태가 일시적으로 다를 수 있다. 시스템은 이러한 중간 상태를 명확하게 정의해야 한다. 이벤트 처리가 실패하면 Retry, 멱등성, DLQ 또는 보상 트랜잭션과 같은 실패 처리 방법을 통해 문제를 해결해야 한다. 실패를 방치하면 시스템이 올바른 최종 상태에 도달하지 못할 수 있다. 또한 최종 상태가 반드시 모든 작업을 이전 상태로 롤백한 상태인 것은 아니다. 주문이 정상적으로 완료되어 COMPLETED가 될 수도 있고, 결제 실패 후 재고를 복원하여 CANCELED가 될 수도 있다. 중요한 것은 정해진 업무 규칙에 맞는 올바른 상태로 수렴하는 것이다.답변에 포함해야 할 키워드
- 중간 상태
- 실패 처리
- 올바른 상태
6. 상황 판단 문제
상황 1
Order는 주문 생성 중 Stock, Payment, Email 서비스를 차례로 동기 호출한다. Email 장애 때문에 주문 API가 30초 뒤 실패했다.
- 주문 핵심 처리와 부가 작업이 어떻게 결합되어 있는가?
- 어떤 작업을 이벤트에 반응하도록 바꾸기 쉬운가?
내 답변
상황 2
메시지 이름이 StockProcess다. 어떤 일이 발생했는지, 무엇을 요청하는지 알 수 없다.
- Command라면 어떤 이름으로 바꿀 수 있는가?
- 재고 차감이 끝난 Event라면 어떤 이름으로 바꿀 수 있는가?
내 답변
상황 3
사용자는 주문 직후 화면에서 주문 처리 중 상태를 보았고 1초 후 결제 완료로 바뀌었다.
- 이 중간 상태는 비동기 흐름에서 왜 자연스러운가?
- 어떤 경우라면 사용자 경험상 문제가 되는가?
내 답변
7. 오늘의 용어 정리
| 용어 | 내가 작성하는 정의 |
|---|---|
| EDA | |
| Event | |
| Command | |
| 동기 통신 | |
| 비동기 통신 | |
| Producer | |
| Broker | |
| Consumer | |
| 느슨한 결합 | |
| 최종적 일관성 |
8. 프로젝트에 한 줄 연결
- Order가 주문 생성 사실을 이벤트로 알리면 Stock이 자기 책임에 따라 반응할 수 있다.
ProductCreated는 Product가 생성되었다는 완료된 사실을 나타내는 과거형 이름이다.- 서비스 간 Kafka 이벤트 통신은 직접 호출에 대한 시간적 결합을 줄인다.
9. 오늘의 자기 점검
- 핵심 개념을 내 말로 설명할 수 있다.
- 오늘 등장한 주요 용어를 구분할 수 있다.
- 주문 시스템에 개념을 적용할 수 있다.
- 이해하지 못한 부분을 표시했다.
- 다음 주제와 현재 주제를 혼동하지 않는다.
10. 이해하지 못한 부분
11. 3문장 요약
12. 복습 퀴즈
- “재고를 차감하라”는 Command와 “재고가 차감되었다”는 Event의 예시 이름을 각각 적어라.
- Producer와 Consumer 사이에서 메시지를 중개하는 것은 무엇인가?
- 동기 통신에서 상대 서비스가 느리면 호출 서비스에는 어떤 영향이 있는가?
- 비동기 처리 중 잠시 데이터가 서로 다른 상태일 수 있지만 결국 맞아지는 성질은 무엇인가?
- 이벤트 이름을 과거형으로 쓰는 가장 중요한 이유는 무엇인가?
복습 퀴즈 정답과 해설 보기
- 예:
DeductStock,StockDeducted. 전자는 행동 요청, 후자는 완료된 사실이다. - Broker다. Producer의 메시지를 받아 Consumer에게 전달한다.
- 응답을 기다리므로 호출 서비스의 지연이나 실패로 이어질 수 있다.
- 최종적 일관성이다.
- 실행할 요청이 아니라 이미 발생한 사실임을 분명히 하기 위해서다.