- ๋ถ์ผ: Spring Kafka, Message Routing, Payload Design
- ๊ด๋ จ ํ์ผ: OrderEventConsumer.java
1. ๋ฐฐ๊ฒฝ: ํ ํฝ ํตํฉ์ผ๋ก ์ธํ ์๋ณ ๋ฌธ์
์ง์ ์, ์นดํ์นด ๋ฉ์์ง ์์ ์ญ์ (Out-of-Order) ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ธ๋ฒคํ ๋ฆฌ ์๋น์ค๋ก ํฅํ๋ ๋ ๊ฐ์ง ๋ช ๋ น(์ฌ๊ณ ์ฐจ๊ฐ, ์ฌ๊ณ ๋ณต๊ตฌ)์ ๊ฐ๋ณ ํ ํฝ์์ ๋จ์ผ ํ ํฝ(inventory.command)์ผ๋ก ํตํฉํจ
๋ฌธ์ ๋ ํ ํฝ์ด ํ๋๋ก ํฉ์ณ์ง๋ฉด์, ์ธ๋ฒคํ ๋ฆฌ ์๋น์ค์ ์ปจ์๋จธ๊ฐ "์ง๊ธ ๋ค์ด์จ ๋ฉ์์ง๊ฐ ์ฐจ๊ฐ ๋ช ๋ น์ธ์ง ๋ณต๊ตฌ ๋ช ๋ น์ธ์ง ์ด๋ป๊ฒ ๊ตฌ๋ถํ ๊ฒ์ธ๊ฐ?" ์ ๋ํ ์์ด๋์ด๊ฐ ์ ๋ ์ค๋ฅด์ง ์์์...
2. ๊ณ ๋ฏผ: ์ ํต์ ์ธ Envelope(๋ดํฌ) ํจํด์ ํ๊ณ
๊ฐ์ฅ ๋จผ์ ๋ ์ฌ๋ฆฐ ํด๊ฒฐ์ฑ ์ ๋ ๊ฐ์ฒด๋ฅผ ํ๋๋ก ๊ฐ์ธ๋ ๊ฑฐ๋ํ '๋ดํฌ(Envelope) DTO'๋ฅผ ๋ง๋๋ ๊ฒ์ด์์
// ๊ตฌ์ํ๋ Envelope DTO ๊ตฌ์กฐ
{
"commandType": "RESERVE", // ๋๋ "RESTORE"
"payload": {
// ์ค์ ๋ฐ์ดํฐ...
}
}
ํ์ง๋ง ์ด ๋ฐฉ์์ ๋์ ํ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ ์น๋ช ์ ์ธ ๋จ์ ์ด ์กด์ฌํจ:
- ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋ ์ฆ๊ฐ: ์ค๋ ์๋น์ค์ ์ธ๋ฒคํ ๋ฆฌ ์๋น์ค ์์ชฝ์ ๊ณตํต Envelope DTO๋ฅผ ๋ง๋ค์ด์ผ ํจ.
- ๋น์ฆ๋์ค ๋ก์ง ์ค์ผ: ์ปจ์๋จธ ๋ด๋ถ์์ if-else ๋๋ switch-case ๋ฌธ์ ํตํด commandType์ ๊ฒ์ฌํ๊ณ ๋ก์ง์ ๋ถ๊ธฐํ๋ ์ง์ ๋ถํ ์ฝ๋๊ฐ ์์ฐ๋จ.
3. ํด๊ฒฐ: Spring Kafka์ __TypeId__ ํค๋์ ๋คํ์ฑ ๋ผ์ฐํ (@KafkaHandler)
๋ช ์์ ์ธ Envelope DTO๋ฅผ ๋ง๋๋ ๋์ , ํ๋ ์์ํฌ๊ฐ ์ ๊ณตํ๋ ๋ฐฉ์์ธ '๋คํ์ฑ(Polymorphic) ๋ผ์ฐํ '์ ํ์ฉํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด๋ณด๋ ค๊ณ ์๋ํจ
[์๋ฆฌ ๋ฐ ์ ์ฉ ๋ฐฉ์]
- ์๋ ํค๋ ์ฝ์ : Spring Kafka๋ JsonSerializer๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ์์ง๋ฅผ ๋ฐํํ ๋, JSON ๋ณธ๋ฌธ(Body)์ ๊ฑด๋๋ฆฌ์ง ์๊ณ ์นดํ์นด ๋ฉ์์ง ํค๋์ __TypeId__: OrderCreatedEventPayload ๋ผ๋ ํ์ ์ ๋ณด๋ฅผ ํจ๊ป ์ฝ์ ํจ.
- Type Mapping: ์ปจ์๋จธ ์ค์ (application.yml)์์ spring.json.type.mapping์ ํตํด ํ๋ก๋์์ ํจํค์ง๋ช ๊ณผ ์ปจ์๋จธ์ ํจํค์ง๋ช ์ 1:1๋ก ๋งคํํจ.
- ํด๋์ค ๋ ๋ฒจ ๊ตฌ๋ ๋ฐ ๋ฉ์๋ ๋ผ์ฐํ : @KafkaListener๋ฅผ ๋ฉ์๋๊ฐ ์๋ ํด๋์ค ๋ ๋ฒจ๋ก ๋์ด์ฌ๋ฆฌ๊ณ , ๊ฐ ๋ฉ์๋์๋ @KafkaHandler๋ฅผ ๋ถ์ฐฉํจ.
@Component
@KafkaListener(topics = "inventory.command") // ๐ก ๋จ์ผ ํ ํฝ ๊ตฌ๋
public class OrderEventConsumer {
// ๐ก TypeId๊ฐ 'OrderCreatedMessage'๋ก ๋งคํ๋๋ฉด ์ด ๋ฉ์๋ ์คํ
@KafkaHandler
public void consumeOrderCreated(OrderCreatedMessage payload) {
stockLockFacade.reserveOrderStocksWithLock(payload);
}
// ๐ก TypeId๊ฐ 'StockRestoreMessage'๋ก ๋งคํ๋๋ฉด ์ด ๋ฉ์๋ ์คํ
@KafkaHandler
public void consumeStockRestoreRequest(StockRestoreMessage payload) {
stockLockFacade.restoreStockWithLock(request);
}
}
4. ์ฑ๊ณผ ๋ฐ ํ๊ณ
์ด ๋ฐฉ์์ ์ฑํํจ์ผ๋ก์จ ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ ์ป์ ์ ์์์:
- ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ(Separation of Concerns): ๊ฐ๋ฐ์๋ ๋ฉ์์ง๋ฅผ ์ด๋ป๊ฒ ๋ผ์ฐํ ํ ์ง ๊ณ ๋ฏผํ ํ์ ์์ด, ๊ฐ ๋น์ฆ๋์ค ๊ฐ์ฒด(OrderCreatedMessage, StockRestoreMessage)์ ์ฒ๋ฆฌ์๋ง ์จ์ ํ ์ง์คํ ์ ์๊ฒ ๋์์.
- ๊ฐ์ฒด ์งํฅ์ ์ค๊ณ: switch-case ๋ฌธ์ ๋ฐฐ์ ํ๊ณ , ํ๋ ์์ํฌ๊ฐ ๋ฐํ์์ ๋คํ์ฑ์ ์ด์ฉํด ์ฌ๋ฐ๋ฅธ ๋ฉ์๋(Handler)๋ฅผ ์ฐพ์์ฃผ๋ ๊ตฌ์กฐ๋ฅผ ์์ฑํจ.
- ์ค๋ฒ ์์ง๋์ด๋ง ๋ฐฉ์ง: ๋ถํ์ํ Envelope DTO๋ฅผ ์์ฑํ์ง ์๊ณ , ์นดํ์นด ํ๋กํ ์ฝ์ Native ๋ฉํ๋ฐ์ดํฐ(Header)๋ฅผ ์ ๊ทน ํ์ฉํ์ฌ ๋คํธ์ํฌ ํ์ด๋ก๋ ํฌ๊ธฐ๊น์ง ์ต์ ํํ์.
๊ฒฐ๊ณผ์ ์ผ๋ก, ๋จ์ผ ํ ํฝ ํตํฉ์ผ๋ก ๋ฐ์ํ ์ ์์๋ ๋ผ์ฐํ ์ ๋ณต์ก์ฑ์ Spring Kafka์ ํต์ฌ ๊ธฐ๋ฅ์ ํ์ฉํจ์ผ๋ก์จ ํด๊ฒฐํด๋ผ ์ ์์์