1. κ°μ
λ€νΈμν¬λ μΈμ λ λΆμμ νλ©°, ν΄λΌμ΄μΈνΈμ μμ²μ΄λ μλ²μ μλ΅μ μΈμ λ μ§ μ μ€λ μ μμ΅λλ€. ν΄λΌμ΄μΈνΈκ° APIλ₯Ό νΈμΆνλλ° νμμμμ΄ λ°μνλ€λ©΄, ν΄λΌμ΄μΈνΈλ μ΄ μμ²μ΄ μλ²μμ μ²λ¦¬λμλμ§ μ κΈΈμ΄ μμ΄ **μ¬μλ(Retry)**λ₯Ό νκ² λ©λλ€. μ΄λ μλ²κ° κ°μ μμ²μ λ λ² μ²λ¦¬νκ² λλ©΄μ λ¬Έμ κ° λ°μν μ μλλ°, μ΄λ₯Ό λ°©μ§νκΈ° μν ν΅μ¬ κ°λ μ΄ λ°λ‘ **λ©±λ±μ±(Idempotency)**μ λλ€.
2. λ©±λ±μ±(Idempotency)μ΄λ?
μνμ΄λ μ μ°νμμ λ©±λ±μ±μ΄λ μ°μ°μ μ¬λ¬ λ² μ μ©νλλΌλ κ²°κ³Όκ° λ¬λΌμ§μ§ μλ μ±μ§μ μλ―Έν©λλ€.
μ¦, f(f(x)) = f(x) κ° μ±λ¦½νλ κ²½μ°μ
λλ€.
μλ² API κ΄μ μμ 보면, **βλμΌν μμ²μ ν λ² λ³΄λ΄λ , 100λ² λ³΄λ΄λ μλ²μ μν(λ°μ΄ν°λ² μ΄μ€ λ±)κ° λμΌνκ² μ μ§λλ μ±μ§β**μ λ§ν©λλ€.
2-1. HTTP λ©μλμ λ©±λ±μ±
RESTful APIλ₯Ό μ€κ³ν λ HTTP λ©μλλ³λ‘ λ©±λ±μ± μ¬λΆκ° λͺ νν ꡬλΆλ©λλ€.
GET: λ°μ΄ν°λ₯Ό μ‘°νλ§ νλ―λ‘ μ¬λ¬ λ² νΈμΆν΄λ μλ² μνλ λ³νμ§ μμ΅λλ€. (λ©±λ±ν¨ O)PUT: νΉμ 리μμ€λ₯Ό ν΅μ§Έλ‘ κ΅μ²΄ν©λλ€. κ°μ λ°μ΄ν°λ‘ μ¬λ¬ λ² κ΅μ²΄ν΄λ μ΅μ’ μνλ λκ°μ΅λλ€. (λ©±λ±ν¨ O)DELETE: 리μμ€λ₯Ό μμ ν©λλ€. μ΄λ―Έ μμ λ 리μμ€λ₯Ό λ€μ μμ νλ € ν΄λ μ΅μ’ μ μΌλ‘ κ·Έ 리μμ€κ° μλ€λ μνλ κ°μ΅λλ€. (λ©±λ±ν¨ O)POST: μλ‘μ΄ λ¦¬μμ€λ₯Ό μμ±ν©λλ€. μ¬λ¬ λ² νΈμΆνλ©΄ μ¬λ¬ κ°μ 리μμ€κ° μμ±λ©λλ€. (λ©±λ±νμ§ μμ X)PATCH: 리μμ€μ μΌλΆλ₯Ό μμ ν©λλ€. λ‘μ§μ λ°λΌ λ©±λ±ν μλ, μλ μλ μμ΅λλ€. (μ:age = 20μ λ©±λ±νμ§λ§,age = age + 1μ λ©±λ±νμ§ μμ΅λλ€.)
3. μ μ€μνκ°? (μ¬μλμ λ°μ΄ν° μ ν©μ±)
μ£Όλ¬Έ κ²°μ μμ€ν μ΄λ μμ½ μ·¨μ μμ€ν μ μκ°ν΄ λ΄ μλ€.
- μ¬μ©μκ° βκ²°μ β λ²νΌμ λλ¦
λλ€. (
POST /payments) - μλ²λ κ²°μ λ₯Ό μλ£νκ³ κ³μ’μμ λμ μ°¨κ°ν©λλ€.
- νμ§λ§ μλ²κ° βκ²°μ μλ£β μλ΅μ 보λ΄κΈ° μ§μ μ λ€νΈμν¬κ° λμ΄μ§λλ€.
- ν΄λΌμ΄μΈνΈλ μλ¬λ₯Ό λ³΄κ³ λ€μ βκ²°μ β λ²νΌμ λλ¦ λλ€(μ¬μλ).
- μλ²λ λ€μ κ²°μ λ₯Ό μ²λ¦¬νμ¬ λμ΄ μ΄μ€ μ°¨κ°λ©λλ€.
μ΄λ¬ν λμ°ν μν©μ λ§κΈ° μν΄ APIλ λ©±λ±μ±μ 보μ₯νλλ‘ μ€κ³λμ΄μΌ ν©λλ€.
sequenceDiagram participant C as Client participant S as Server participant DB as Database C->>S: 1. μμ½ μ·¨μ μμ² (Retry 1) S->>DB: 2. μν λ³κ²½ (CONFIRMED -> CANCELLED) DB-->>S: 3. λ°μ μλ£ S--xC: 4. μλ΅ μ μ‘ μ€ λ€νΈμν¬ μ μ€! Note over C: Timeout λ°μ, μ¬μλ μν C->>S: 5. μμ½ μ·¨μ μμ² (Retry 2) S->>DB: 6. μν μ‘°ν (μ΄λ―Έ CANCELLED) S-->>C: 7. 200 OK (λ©±λ±μ± 보μ₯: μΆκ° λ³κ²½ μμ)
4. λ©±λ±μ±μ ꡬννλ λ°©λ²
4-1. κ³ μ ν Idempotency Key μ¬μ©
κ°μ₯ 보νΈμ μΈ λ°©λ²μ ν΄λΌμ΄μΈνΈκ° μμ²μ λ³΄λΌ λ ν€λμ κ³ μ ν Idempotency-Key (UUID λ±)λ₯Ό ν¬ν¨νμ¬ λ³΄λ΄λ κ²μ
λλ€.
μλ²λ μ΄ ν€λ₯Ό Redisλ DBμ μ μ₯ν΄ λκ³ , λμΌν ν€λ‘ μμ²μ΄ λ€μ΄μ€λ©΄ λ‘μ§μ μννμ§ μκ³ μ΄μ μ μ±κ³΅νλ μλ΅μ κ·Έλλ‘ λ°νν©λλ€. Stripeμ κ°μ κ²°μ APIλ€μ΄ μ΄ λ°©μμ μ±νν©λλ€.
4-2. λ°μ΄ν°λ² μ΄μ€ μν μ²΄ν¬ (Optimistic λ©±λ±μ±)
λΉμ¦λμ€ λ‘μ§ λ¨μμ μν°ν°μ νμ¬ μνλ₯Ό 체ν¬νμ¬ λ©±λ±μ±μ 보μ₯ν μλ μμ΅λλ€.
μλ₯Ό λ€μ΄, μ·¨μ μμ²μ΄ λ€μ΄μμ λ λμμ μνκ° μ΄λ―Έ CANCELLEDλΌλ©΄ μλ¬λ₯Ό λμ§κ±°λ μ±κ³΅(200 OK)μ λ°ννκ³ λ‘μ§μ μ‘°κΈ° μ’
λ£(Return)νλ λ°©μμ
λλ€.
@Transactional
public void cancelBooking(String bookingId) {
Booking booking = repository.findById(bookingId);
// λ©±λ±μ± 보μ₯ λ‘μ§
if (booking.getStatus() == BookingStatus.CANCELLED) {
return; // μ΄λ―Έ μ·¨μλμμΌλ―λ‘ μ무 μμ
λ νμ§ μμ
}
booking.cancel();
}μμ νκ³ κ²¬κ³ ν λΆμ° μμ€ν μ ꡬμΆνκΈ° μν΄ λ©±λ±μ±μ μ νμ΄ μλ νμμ λλ€.