C.W.K.
Stream
Lesson 04 of 05 · published

409 Challenge

~11 min · http-status, confirmation, honest-errors, api-design

Level 0Open Gate
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"앱이 확신 못 할 때 정직한 수는 추측이 아냐 — 명확히 라벨된 선택을 너한테 건네는 거야."

실패한 refresh 는 숨길 에러가 아냐

Keep 한테 quote refresh 를 요청했는데 primary provider 가 실패하면, 유혹적이지만 틀린 수가 셋 있어: stale 값을 조용히 내기, fallback 으로 조용히 swap, 일반 500 으로 터지기. Keep 은 아무것도 안 해. 409 — 해결이 필요한 conflict 의 HTTP 상태 — 를 candidate fallback payload 와 함께 반환해. 그 409 는 confirmation challenge 야: 'primary 가 답 안 했어; 대신 쓸 수 있는 fallback 이 여기 있어; 원해?' 진행 거부가 막다른 길이 아니라 구조화된 질문이 돼.

일급 응답으로서의 confirmation

핵심 설계 아이디어는 '뭔가 확인이 필요해' 를 에러나 조용한 기본값이 아니라 정당하고 구조화된 API 응답으로 다루는 거야. FallbackConfirmationRequired 는 정확한 candidate 를 붙인 409 로 매핑돼서, 프론트엔드가 진짜 선택을 렌더할 수 있어 — 어느 provider, 어느 ticker, 무슨 값을 보여주며 — 사람이 완전한 정보로 받거나 거절해. 대안과 비교해: 조용한 swap 은 선택을 안 줘; 500 은 정보를 안 줘; stale 값은 거짓말을 줘. payload 딸린 409 는 agency 를 줘.

'확인 필요' 는 변장이 아니라 자기 상태를 받을 자격이 있어. 시스템이 사람 결정 없이 진짜로 진행 못 할 때, 그걸 사람이 결정하는 데 필요한 전부를 지닌 별개의 구조화된 응답으로 인코딩하는 게 조용한 기본값이나 일반 실패보다 정직해. 그 요청을 삼키는 예외가 아니라 API 의 일급 부분으로 만들어.

끝까지 정직한 상태

409 는 더 넓은 규칙의 한 사례야: Keep 의 도메인 에러는 뭉뚱그린 500 대신 정직한 HTTP 상태를 지녀.

  • FallbackConfirmationRequired → candidate payload 딸린 409
  • 나쁜 입력값 → 400
  • 없는 simulation run → 404
  • provider/runtime 실패 → 503

각 상태가 client 한테 뭐가 잘못됐고 retry·입력수정·확인 중 뭐가 옳은 다음 수인지에 대해 참인 걸 말해. 이 전부에 일반 500 이면 호출자가 필요한 정보를 정확히 지워. 그리고 새 provider 실패는 진짜 RuntimeError → 503 을 raise 하지, 데이터인 척하는 sentinel 값을 절대 안 내.

sentinel 값 함정: 실패 시 -1 이나 0 이나 null 반환. provider 실패 시 가짜 숫자를 반환하는 함수는 진짜 데이터랑 똑같이 생긴 거짓말을 downstream 으로 밀어. Keep 은 이걸 금지해 — 실패는 raise 하고, raise 는 정직한 상태가 돼. 못 믿을 숫자는 믿을 수 있는 에러보다 나빠, 에러는 멈추고 가짜 숫자는 퍼지니까.

Code

도메인 에러가 정직한 상태로 매핑 (예시)·python
# 각 도메인 에러는 그것에 대한 진실을 말하는 상태를 지녀.
ERROR_STATUS = {
    FallbackConfirmationRequired: 409,   # candidate 여기 — 확인?
    ValueError:                   400,   # 입력이 나빴어
    MissingRun:                   404,   # 그 simulation 없어
    RuntimeError:                 503,   # provider/runtime 실패, 나중에
}

@app.exception_handler(FallbackConfirmationRequired)
def needs_confirmation(request, exc):
    # 500 아냐. 할 정확한 선택을 지닌 구조화된 409.
    return JSONResponse(status_code=409, content={
        "needs_confirmation": True,
        "candidate_source": exc.candidate.source,
        "candidate_value":  exc.candidate.value,
        "ticker":           exc.candidate.ticker,
    })

External links

Exercise

여러 다른 실패 이유에 500(또는 -1/null 같은 sentinel)을 반환하는 네 endpoint 를 찾아. 그 이유들을 정직한 상태로 나눠: 어느 게 400(나쁜 입력), 404(없음), 409(결정 필요), 503(나중에 재시도)? 409 경우엔 client 가 그냥 'conflict' 만 보는 대신 실제로 결정할 수 있게 붙일 payload 를 묘사해.
Hint
payload 없는 409 는 500 보다 겨우 조금 나아 — client 는 뭔가 해결이 필요한 건 알지만 뭔지는 몰라. 힘은 붙은 candidate 에 있어: 대체가 정확히 뭐일지 보여줘서, '확인?' 이 눈감은 yes/no 가 아니라 정보 있는 진짜 선택이 되게.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.