C.W.K.
Stream
Lesson 06 of 06 · published

Method Decision Table — 계약 맞는 verb 골라

~10 min · semantics, methods, decision, synthesis

Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"영어 verb 를 HTTP method 로 번역 그만해. 'Create' 가 항상 POST 아냐; 'update' 가 항상 PUT 아냐. 선택은 세 invariant — safety, idempotency, cacheability — 가 이끌어, 사전 아냐."

결정 tree

설계하는 모든 operation 에 대해, 이 tree 를 위에서 아래로 걸어:

1. 관찰 가능한 server state 바꿔?
   NO  → GET (혹은 header 만 필요하면 HEAD, method 발견은 OPTIONS)
   YES → 2 계속

2. Client 가 정확한 target URI 알아?
   NO  → POST (server 가 URI 할당; 201 + Location 돌려줌)
   YES → 3 계속

3. Resource 의 FULL representation 보내?
   YES → PUT (idempotent: 같은 payload → 같은 state)
   NO  → PATCH (부분 update; diff/delta 운반)

4. Resource 완전 제거?
   → DELETE

5. 명백한 target resource 없는 operation ('command' 스타일)?
   → action 이름 URI 에 POST (예: POST /charges/42/refund)

Method/Outcome 매트릭스

대부분 operation 이 이 패턴 중 하나에 맞아:

의도MethodURI shape성공 시 status code
Resource 읽기GET/users/42200 OK (혹은 cached 면 304)
Resource 나열GET/users?role=admin200 OK
존재 싸게 테스트HEAD/users/42200 OK (body 없음)
허용 method 발견OPTIONS/users/42200 OK + Allow header
Server 할당 ID 로 생성POST/users (컬렉션)201 Created + Location
Client 할당 ID 로 생성PUT/users/client-picked-id201 Created
기존 resource 교체PUT/users/42200 OK (혹은 204)
부분 updatePATCH/users/42200 OK
제거DELETE/users/42204 No Content
Async / long-runningPOST/jobs (컬렉션)202 Accepted + Location
Command / actionPOST/charges/42/refund200 OK or 202
파일 uploadPOST/uploads201 Created

풀어쓴 예 셋

"User 42 한테 환영 이메일 보내." 이메일 자체엔 명백한 target resource 없음; operation 에 side effect 있음 (이메일 나감); idempotent 아님 (두 번 보내면 이메일 두 개). Action URI 에 POST: POST /users/42/welcome-emails 혹은 수신자 명시한 body 가진 POST /emails. 안전한 retry 원하면 Idempotency-Key header 추가.

"User 42 의 이메일 주소 update." Target URI 있음; client 가 resource 알음; 부분 update. {"email": "new@example.com"} 로 PATCH. 혹은 API 가 PUT-everything 스타일이면 (어떤 건 그래) 전체 user payload 로 PUT.

"Payment 42 환불." "환불 operation" 에 명백한 자연 resource 없음; operation 에 side effect 있음; 협조 없이 거의 확실히 idempotent 아님. Action URI 에 POST: POST /payments/42/refunds. Idempotency-Key 추가. Response 는 201 (Refund resource 생성됨) 이나 200 (operation 성공) 될 수 있음.

가장 가까운 영어 단어 아닌 계약 맞는 verb 골라. 'Update' 가 PUT 의미 아님; 'create' 가 POST 의미 아님; 'remove' 가 DELETE 의미 아님. Method 는 세 invariant 가 골라 — operation 이 state 변경, retry 안전성, cacheability 에 대해 뭘 약속해? Tree 걸어; verb 가 떨어져 나와.

'모든 거 POST 로 터널' 안티패턴

어떤 팀은 모든 endpoint 를 POST 로 기본. "항상 동작. 생각할 필요 없어." 비용 실제:

  • CDN 이 response 캐시 못 함 (POST 기본 cacheable 아님).
  • Client 가 transport 실패에 안전 retry 못 함 (POST not idempotent).
  • 브라우저와 HTTP intermediary 가 최적화 못 함 (speculative prefetch 없음, conditional GET 없음).
  • 로그와 dashboard 가 진단 신호 잃음 — 모든 operation 이 "POST 뭔가 일어남" 처럼 보임.
  • OpenAPI / Swagger 문서가 shape 없는 POST 벽이 됨.

Protocol 이 verb 일곱 주는 이유가 정확히 이 정보가 wire 에 있게 하려고. 버리면 system 이 덜 관찰 가능, 덜 cacheable, 덜 retryable. 하지 마.

cwkPippa 의 method 선택

backend/routes/ 걸으면 의도적 선택 곳곳에 있어. POST /api/conversations 는 server 할당 ID 로 생성; GET /api/conversations/{id} 는 읽기 (healing 층이 client 한텐 투명); PUT /api/conversations/{id}/title 는 이름 변경 (idempotent — 같은 이름 → 같은 state); PATCH /api/folders/{id} 는 색이나 display name 변경; DELETE /api/conversations/{id} 는 제거; POST /api/council/{id}/finalize 는 command (not idempotent — 두 번 finalize 면 버그). 이 모든 선택이 tree 걸어 나옴.

Code

결정 helper 함수 — 설계 review 때 써·python
# Code review 나 설계 논의용 'method picker' helper
from dataclasses import dataclass

@dataclass
class Operation:
    changes_state: bool
    target_uri_known: bool
    sends_full_resource: bool
    is_removal: bool
    has_natural_target: bool

def pick_method(op: Operation) -> str:
    if not op.changes_state:
        return 'GET (존재 체크엔 HEAD, 발견엔 OPTIONS)'
    if op.is_removal:
        return 'DELETE'
    if not op.has_natural_target:
        return 'Action URI 에 POST (예: POST /resource/{id}/action)'
    if not op.target_uri_known:
        return 'Collection 에 POST (server 가 ID 할당, 201 + Location 돌려줌)'
    if op.sends_full_resource:
        return 'PUT (idempotent — 전체 교체)'
    return 'PATCH (patch format 이 보장할 때만 idempotent)'

# 예 걸어보기
print(pick_method(Operation(changes_state=False, target_uri_known=True,
                            sends_full_resource=False, is_removal=False,
                            has_natural_target=True)))
# GET (존재 체크엔 HEAD, 발견엔 OPTIONS)

print(pick_method(Operation(changes_state=True, target_uri_known=False,
                            sends_full_resource=False, is_removal=False,
                            has_natural_target=True)))
# Collection 에 POST (server 가 ID 할당, 201 + Location 돌려줌)
cwkPippa-스타일 route: 각 method 가 선택된 계약·python
# cwkPippa-스타일 FastAPI route — 각 method 선택이 의도적
from fastapi import FastAPI, APIRouter, status

app = FastAPI()
conversations = APIRouter(prefix='/api/conversations')

@conversations.get('/{cid}')
async def read_conversation(cid: str):
    # State 변경 없음, target URI 알려짐 → GET
    return await load_from_db(cid)

@conversations.post('', status_code=status.HTTP_201_CREATED)
async def create_conversation():
    # Server 가 ID 할당; 201 + Location 돌려줌
    new_id = await create_in_db()
    return {'id': new_id, 'location': f'/api/conversations/{new_id}'}

@conversations.put('/{cid}/title')
async def rename_conversation(cid: str, payload: dict):
    # Idempotent: 같은 title → 같은 state. PUT 이 맞음.
    await update_title(cid, payload['title'])
    return {'id': cid, 'title': payload['title']}

@conversations.patch('/{cid}')
async def update_conversation(cid: str, payload: dict):
    # 부분 update: payload 가 필드의 어느 부분집합이든 가질 수 있음. PATCH.
    return await apply_patch(cid, payload)

@conversations.delete('/{cid}', status_code=status.HTTP_204_NO_CONTENT)
async def delete_conversation(cid: str):
    await delete_from_db(cid)  # DELETE — idempotent

@conversations.post('/{cid}/finalize', status_code=status.HTTP_202_ACCEPTED)
async def finalize_council(cid: str):
    # Side effect 있는 command/action, idempotent 아님 — POST
    job_id = await enqueue_finalize(cid)
    return {'job_id': job_id, 'poll': f'/api/jobs/{job_id}'}

External links

Exercise

이 operation 열 개 받고 각각에 맞는 HTTP method 골라. 각각에 세 invariant (safe / idempotent / cacheable) 써서 한 문장 정당화: (1) user 42 profile fetch, (2) user 42 email 필드 update, (3) user 42 전체 profile 교체, (4) user 42 제거, (5) body 안 download 하고 user 42 존재 확인, (6) user 42 한테 비밀번호 재설정 이메일, (7) 폴더에 새 대화 생성, (8) 대화 X 를 폴더 Y 로 이동, (9) council round finalize, (10) /users/42 에 허용된 method 발견.
Hint
Lesson 의 결정 tree 써. (1) GET — safe + idempotent. (2) PATCH — 부분 update, 꼭 idempotent 아님. (3) PUT — 전체 교체, idempotent. (4) DELETE. (5) HEAD. (6) POST + Idempotency-Key — side effect, not idempotent. (7) POST — server 가 대화 ID 할당. (8) PATCH 의 'parent_folder' 필드에 PUT, 혹은 /conversations/{id}/folder 에 PUT. (9) POST + Idempotency-Key — command, side effect. (10) OPTIONS. 보너스: 이 중 어떤 게 date-based versioning 전략 으로부터 가장 이득 볼지 식별.

Progress

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

댓글 0

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

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