- ๋ถ์ผ: ๋์์ฑ ์ ์ด, Event-Driven Architecture
- ๊ด๋ จ ํ์ผ: InventoryOutboxHelper.java
1. ๋ฐ๋จ ๋ฐ 1์ฐจ ์๋ (๋ถ์์ ํ ํด๊ฒฐ…)
Outbox ํจํด์ ๋์ ํ์ฌ DB์ ์ด๋ฒคํธ๋ฅผ ์ ์ฌํ ํ, 5์ด๋ง๋ค ๋๋ ์ค์ผ์ค๋ฌ๊ฐ ์นดํ์นด๋ก ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๋๋ก ๊ตฌ์ฑํจ. ์ด๊ธฐ์๋ ๋๊ธฐ(get()) ๋ฐฉ์์ผ๋ก ๋ฐ์กํ์ผ๋, ์ค์ผ์ค๋ฌ์ ์ฒ๋ฆฌ๋์ ๋์ด๊ธฐ ์ํด ๋น๋๊ธฐ(whenComplete()) ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํ๊ณ ์คํจ ์ retryCount๋ฅผ ์ฆ๊ฐ์ํค๋๋ก ๋ก์ง์ ์์ฑํ์
2. ๋ฌด์์ด ๋ฌธ์ ์๋?
๋น๋๊ธฐ ์ฒ๋ฆฌ ๋์ ํ, ๊ฐํ์ ์ผ๋ก "์ด๋ฏธ ์นดํ์นด ๋ฐ์ก์ ์ฑ๊ณตํ ์ด๋ฒคํธ๊ฐ FAILED ์ํ๋ก ๋ฎ์ด์์์ง๋ ํ์"์ด ๋ฐ๊ฒฌ๋จ. ์์ธ์ ์นดํ์นด ๋ธ๋ก์ปค์ ์ผ์์ ์ง์ฐ์ผ๋ก ์ธํ Race Condition์ด์์
- ์ค๋ ๋ A(์ฒซ ๋ฒ์งธ ํด๋ง)๊ฐ ๋ฉ์์ง๋ฅผ ์๊ณ ํ์์์์ ๊ธฐ๋ค๋ฆผ
- ํ์์์์ด ๋ฐ์ํ๊ธฐ ์ง์ , ์ค๋ ๋ B(๋ค์ ํด๋ง)๊ฐ ๋์ผํ ๋ฉ์์ง๋ฅผ ๋ค์ ์ฝ์ด ์นดํ์นด๋ก ์๊ณ ์ฑ๊ณต(PUBLISHED) ์ฒ๋ฆฌํจ
- ์งํ, ์ค๋ ๋ A์ ์คํจ ์ฝ๋ฐฑ์ด ๋ค๋ฆ๊ฒ ๋์ฐฉํ์ฌ ์ํ๋ฅผ ๋ค์ FAILED๋ก ๋ฎ์ด์์
3. ํด๊ฒฐ๋ฐฉ๋ฒ: ์ํ ๊ฐ๋
๋ถ์ฐ ํ๊ฒฝ์ ๋น๋๊ธฐ ์ฝ๋ฐฑ์ ๋์ฐฉ ์์๋ฅผ ๋ณด์ฅํ์ง ์์. ๋ฐ๋ผ์ DB์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ , ํ์ฌ ๋ฐ์ดํฐ๊ฐ ๋ด๊ฐ ๊ธฐ๋ํ๋ ์ํ(์: INIT)๊ฐ ๋ง๋์ง ํ์ธํ๋ ๋ก์ง์ด ํ์์ ์ด๋ผ๊ณ ์๊ฐํจ
4. ์ต์ข ํด๊ฒฐ ๋ฐ ์ค์ ์ฝ๋
DB์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ์งํ, ์ํ๊ฐ INIT์ด ์๋ ๊ฒฝ์ฐ ์ฆ์ ๋ก์ง์ ์ค๋จํ๋ ๋ฉฑ๋ฑ์ฑ ๋ณด์ฅ ๋ก์ง์ ์ถ๊ฐํจ
// ๋น๋๊ธฐ ์นดํ์นด ์ ์ก ์คํจ ์ ์ฌ์๋ ํ์๋ฅผ ์ฌ๋ฆฌ๊ณ 3ํ ์ด๊ณผ ์ FAILED ์ฒ๋ฆฌํ๋ ๋ก์ง
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handleFailure(UUID outboxId) {
outboxRepository.findById(outboxId).ifPresentOrElse(
outbox -> {
// ๋น๋๊ธฐ ์ฝ๋ฐฑ ์ง์ฐ์ผ๋ก ์ธํ Race Condition ๋ฐฉ์ด (์ํ ๊ฐ๋)
// ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ด๋ฏธ ์ฑ๊ณต(PUBLISHED)์์ผฐ๊ฑฐ๋ ์๊ตฌ ์คํจ(FAILED) ์ฒ๋ฆฌํ๋ค๋ฉด ๋ฌด์
if (outbox.getStatus() != OutboxStatus.INIT) {
log.warn("Outbox ์ํ ๋ณ๊ฒฝ ๋ฌด์(์ด๋ฏธ ์ฒ๋ฆฌ๋จ). outboxId={}", outboxId);
return;
}
outbox.incrementRetryCount();
if (outbox.getRetryCount() >= maxRetries) {
outbox.markAsFailed();
log.error("[CRITICAL] Order Outbox ์ต๋ ์ฌ์๋ ํ์ ์ด๊ณผ. FAILED ์ํ๋ก ๋งํน. ์๋ ์กฐ์น ์๋ง. outboxId={}", outboxId);
}
outboxRepository.save(outbox);
},
() -> log.warn("[Order Outbox] ์คํจ์ฒ๋ฆฌ๋์ ์์ outboxId={}", outboxId)
);
}