์๋์ด ๊ฐ๋ฐ์๋ฅผ ์ํ ๋ฐ์ด๋ธ์ฝ๋ฉ ๊ณ ๊ธ ์ฃผ์ : ๊ฐ(ๆ)์ ์์คํ ์ผ๋ก ๋ง๋๋ ์ค๊ณ์ ์ด์
์๋์ด ๊ฐ๋ฐ์๋ฅผ ์ํ ๋ฐ์ด๋ธ์ฝ๋ฉ ๊ณ ๊ธ ์ฃผ์ : ๊ฐ(ๆ)์ ์์คํ ์ผ๋ก ๋ง๋๋ ์ค๊ณ์ ์ด์ ๋์ : โ์ ๋๋ ๋ โ์ ์ฌํ ๊ฐ๋ฅํ ์ค๋ ฅ์ผ๋ก ๋ฐ๊พธ๋...
์๋์ด ๊ฐ๋ฐ์๋ฅผ ์ํ ๋ฐ์ด๋ธ์ฝ๋ฉ ๊ณ ๊ธ ์ฃผ์ : ๊ฐ(ๆ)์ ์์คํ ์ผ๋ก ๋ง๋๋ ์ค๊ณ์ ์ด์
๋์ : โ์ ๋๋ ๋ โ์ ์ฌํ ๊ฐ๋ฅํ ์ค๋ ฅ์ผ๋ก ๋ฐ๊พธ๋ ์๊ฐ
๋ฐ์ด๋ธ์ฝ๋ฉ์ ๋จ์ํ ๋น ๋ฅด๊ฒ ์ฝ๋๋ฅผ ์น๋ ๊ธฐ์ ์ด ์๋๋๋ค. ๋ฌธ์ ๋ฅผ ์ฝ๋ ๊ฐ, ๊ตฌ์กฐ๋ฅผ ์ก๋ ๊ฐ, ๋ฒ๊ทธ๋ฅผ ์ค์ด๋ ๊ฐ, ํ์ด ์ดํดํ๋ ๊ฐโ์ด๋ฐ ๊ฒ๋ค์ด ํ ๋ฒ์ ๋ง์๋จ์ด์ง ๋ โ์ค๋์ ์ ํ๋ฆฐ๋คโ๋ ๋๋์ด ์ค์ฃ . ์๋์ด์๊ฒ ์ค์ํ ๊ฑด ๊ทธ ๊ฐ์ ์ฌํ ๊ฐ๋ฅํ ์ฒด๊ณ๋ก ๋ง๋๋ ์ผ์ ๋๋ค. ์ค๋์ ๋ฐ์ด๋ธ์ฝ๋ฉ์ โ๊ธฐ๋ถ ์ข์ ์๋งโ์์ ์ค๊ณยทํ ์คํธยท์ด์๊น์ง ์ด์ด์ง๋ ๊ณ ๊ธ ์ต๊ด์ผ๋ก ๋์ด์ฌ๋ฆฌ๋ ์ฃผ์ ๋ฅผ ์ ๋ฆฌํฉ๋๋ค.
์์ฝ
- ๋ฐ์ด๋ธ์ฝ๋ฉ์ ํต์ฌ์ ์๋(Contract) โ ๊ฒฝ๊ณ(Boundary) โ ํผ๋๋ฐฑ(Feedback) ๋ฃจํ๋ฅผ ๋น ๋ฅด๊ฒ ๋๋ ๊ฒ
- ๊ณ ๊ธ ๋จ๊ณ์์๋ ๋ถ๋ณ์/์คํจ ๋ชจ๋/๊ด์ธก์ฑ์ ๋จผ์ ๋๊ณ ์ฝ๋๊ฐ ๋ฐ๋ผ์ค๊ฒ ๋ง๋ ๋ค
- โ์๋โ๋ ์์ด ์๋๋ผ ์ ์ฝ์ ์ ๊ฑฐ๋ ์ค๊ณ์์ ๋์จ๋ค
๋ฐ์ด๋ธ์ฝ๋ฉ์ ๊ณ ๊ธ ํ๋ ์: Contract โ Boundary โ Feedback
1) Contract: โ๋ฌด์์ด ์ฐธ์ด์ด์ผ ํ๋๊ฐโ๋ฅผ ๋จผ์ ๋ง๋ก ๊ณ ์
์๋์ด ๋ฐ์ด๋ธ๋ ๊ตฌํ๋ณด๋ค ๋ถ๋ณ์(invariant)์ ๋จผ์ ์ ์ธํฉ๋๋ค.
- ์ ๋ ฅ์ ์ด๋ค ์ ์ฝ์ ๊ฐ๋๊ฐ?
- ์ถ๋ ฅ์ ์ด๋ค ๋ณด์ฅ์ ํ๋๊ฐ?
- ์คํจ๋ ์ด๋ค ํํ๋ก ๋๋ฌ๋ด๋๊ฐ? (์์ธ/์๋ฌ๊ฐ/์ฌ์๋) ์: โ์ฃผ๋ฌธ ์์ฑ์ ๋ฉฑ๋ฑํด์ผ ํ๋ค. ๋์ผํ idempotency key๋ฉด ๊ฒฐ๊ณผ๊ฐ ๊ฐ์์ผ ํ๋ค.โ
2) Boundary: ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ์ด ํฐ ๊ณณ์ ๊ฒฝ๊ณ๋ฅผ ์ธ์ด๋ค
๋ฐ์ด๋ธ์ฝ๋ฉ์ด ํ์์ ๋นจ๋ผ์ง๋ ์ด์ ๋ โ๋์ถฉ ์ง๋ ๋์๊ฐโ์ด ์๋๋ผ, ๋ฐ๋ ๊ณณ๊ณผ ์ ๋ฐ๋ ๊ณณ์ ๋ถ๋ฆฌํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
- ๋๋ฉ์ธ ๋ก์ง์ ์์ํ๊ฒ
- I/O(DB, ๋คํธ์ํฌ, ํ์ผ)๋ ์ด๋ํฐ๋ก
- ์๊ฐ/๋๋ค/์ธ๋ถ ์์กด์ฑ์ ์ฃผ์ ์ผ๋ก
3) Feedback: ์๋ ๊ฐ๊ฐ์ ํ ์คํธ์ ๋ก๊ทธ๋ก ์ธ๋ถํ
๊ฐ์ ํ๋ค๋ฆฝ๋๋ค. ๊ทธ๋์ ๊ฐ์ ์๋ ํ์ธ์ผ๋ก ์ฎ๊น๋๋ค.
- ์์ ๋จ์ ํ ์คํธ(๋ถ๋ณ์ ๊ฒ์ฆ)
- ์คํจ ๋ชจ๋ ํ ์คํธ(ํ์์์, ์ค๋ณต ์์ฒญ, ๋ถ๋ถ ์คํจ)
- ์ด์ ๊ด์ธก(์์ฒญ ID, ์งํ, ๊ฒฝ๊ณ ๊ธฐ์ค)
์ฌ๋ก 1: โ๋ฉฑ๋ฑ ์ฃผ๋ฌธ ์์ฑโ์ ๋ฐ์ด๋ธ์ฝ๋ฉ์ผ๋ก ์์ ํ๊ฒ
์๋๋ ํ์ด์ฌ ์์์ ๋๋ค. ํต์ฌ์ idempotency key ์ ์ฅ์๋ฅผ ๊ฒฝ๊ณ๋ก ๋ถ๋ฆฌํ๊ณ , ๋๋ฉ์ธ์ ์ต๋ํ ๋จ์ํ๊ฒ ์ ์งํ๋ ๊ฒ๋๋ค.
from dataclasses import dataclass
from typing import Optional, Protocol
import uuid
@dataclass(frozen=True)
class Order:
order_id: str
user_id: str
amount: int
class IdempotencyStore(Protocol):
def get(self, key: str) -> Optional[Order]: ...
def put_if_absent(self, key: str, order: Order) -> bool: ...
class OrderService:
def __init__(self, store: IdempotencyStore):
self.store = store
def create_order(self, user_id: str, amount: int, idem_key: str) -> Order:
# Contract: amount๋ ์์, user_id๋ ๋น์ด์์ง ์์
if not user_id or amount <= 0:
raise ValueError("invalid input")
existing = self.store.get(idem_key)
if existing:
return existing # ๋ฉฑ๋ฑ ๋ณด์ฅ
new_order = Order(order_id=(uuid.uuid4()), user_id=user_id, amount=amount)
inserted = .store.put_if_absent(idem_key, new_order)
inserted:
existing = .store.get(idem_key)
existing:
existing
RuntimeError()
new_order
์ด ์ฝ๋์ โ๋ฐ์ด๋ธ ํฌ์ธํธโ
- ๋๋ฉ์ธ์ ๋จ์ํฉ๋๋ค: ๊ฒ์ฆ โ ๊ธฐ์กด ๊ฐ ๋ฐํ โ ์ ๊ฐ ์๋
- ๊น๋ค๋ก์ด ๋ถ๋ถ(๋์์ฑ)์ store ์ธํฐํ์ด์ค ๋ค๋ก ์จ๊น๋๋ค
- ์คํจ ๋ชจ๋๋ฅผ ๋ช ์์ ์ผ๋ก ๋ค๋ฃน๋๋ค: โ์ผ๊ด์ฑ ๊นจ์งโ์ ์กฐ์ฉํ ๋์ด๊ฐ์ง ์์
์ฌ๋ก 2: ๋น ๋ฅธ ๋ฆฌํฉํฐ๋ง์ ์ํ โ์๋ฌ ๋ชจ๋ธโ ํต์ผ
์๋์ด ํ์์ ๋ฐ์ด๋ธ๊ฐ ๊นจ์ง๋ ์๊ฐ์ ์๋ฌ๊ฐ ์ ๋ฉ๋๋ก์ผ ๋์ ๋๋ค. ์์ธ๊ฐ ๋ ์๋ค๋๊ณ , ๋ฉ์์ง๋ ์ ๊ฐ๊ฐ์ด๊ณ , ํธ์ถ์๋ ๋งค๋ฒ ๋ค๋ฅด๊ฒ ์ฒ๋ฆฌํ์ฃ . ๊ณ ๊ธ ๋ฐ์ด๋ธ์ฝ๋ฉ์ ์๋ฌ๋ฅผ ๋๋ฉ์ธ ์ธ์ด๋ก ํต์ผํฉ๋๋ค.
ValidationError(์ ๋ ฅ ๋ฌธ์ )ConflictError(์ค๋ณต/๊ฒฝ์)TransientError(์ฌ์๋ ๊ฐ์น ์์)PermanentError(์ฌ์๋ ๋ฌด์๋ฏธ) ์ด๋ ๊ฒ๋ง ์ก์๋ ์ฝ๋ ๋ฆฌ๋ทฐ ๋น์ฉ์ด ์ค๊ณ , ์ฅ์ ๋์์ด ๋นจ๋ผ์ง๋๋ค.
์๋์ด์๊ฒ ํนํ ์ค์ํ ์ด์ ๊ด์ : โ๊ด์ธก ๊ฐ๋ฅํ ๋ฐ์ด๋ธโ
๋ฐ์ด๋ธ์ฝ๋ฉ์ ์ด์์์ ์์ฑ๋ฉ๋๋ค. ์ต์ํ ์๋๋ ์ต๊ด์ผ๋ก ๋๋ฉด ์ข์ต๋๋ค.
- ์์ฒญ๋ง๋ค correlation id๋ฅผ ๋ถ์ฌ ํ๋ฆ์ ์ถ์
- ํต์ฌ ๊ฒฝ๋ก์ ํ ์ค์ ๊ตฌ์กฐํ ๋ก๊ทธ: โ์ ๋ ฅ ์์ฝ + ๊ฒฐ๊ณผ + ์ง์ฐโ
- ์ฌ์๋/ํ์์์/ํ๋ก์ฐจ๋จ ๊ฐ์ ์คํจ ์ค๊ณ๋ฅผ โ๋์ค์โ๋ก ๋ฏธ๋ฃจ์ง ์๊ธฐ
๊ฒฐ๋ก : ๋ฐ์ด๋ธ์ฝ๋ฉ์ ๊ฐ๊ฐ์ด ์๋๋ผ ์ค๊ณ๋ ๋ฃจํ๋ค
์๋์ด์ ๋ฐ์ด๋ธ์ฝ๋ฉ์ โ์์ด ๋น ๋ฅธ ๊ฐ๋ฐโ์ด ์๋๋ผ ๋ถ๋ณ์์ ๋จผ์ ์ธ์ฐ๊ณ , ๊ฒฝ๊ณ๋ฅผ ๋ถ๋ฆฌํ๊ณ , ํผ๋๋ฐฑ์ ์๋ํํ๋ ๊ฐ๋ฐ์ ๋๋ค. ์ค๋ ์ ๋ ์ฝ๋๋ฅผ ๋ด์ผ๋, ๋ค๋ฅธ ํ์๋, ์ด์ ํ๊ฒฝ์์๋ ์ฌํํ ์ ์๊ฒ ๋ง๋๋ ๊ฒโ๊ทธ๊ฒ ๊ณ ๊ธ ๋ฐ์ด๋ธ์ ์ ์์ ๋๋ค.
โฌ๏ธ ์ด ๊ธ์ด ๋์์ด ๋์ จ๋ค๋ฉด, ์๋ ๊ด๊ณ ๋ฅผ ํ ๋ฒ๋ง ํด๋ฆญํด์ฃผ์ธ์! ์ ์๊ฒ ํฐ ํ์ด ๋ฉ๋๋ค ๐โโ๏ธ โฌ๏ธ