LabHub
배우기 러닝패스 코스

FastAPI — 타입이 곧 계약이다 · 오류 응답도 버전이 있는 API다 · 실습

오류 응답도 버전이 있는 API다

LabHub 에서 이어서 보기

목표

업무 예외와 내부 오류를 구분하고 일관된 문제 응답을 제공합니다.

왜 중요한가

클라이언트는 오류가 나면 detail 문자열을 정규식으로 읽고 있었다. 어느 날 문장이 바뀌자 재고 부족도 결제 재시도로 처리됐다. 다른 경로에서는 예외 문자열이 그대로 공개되어 SQL과 내부 경로가 새어 나갔다. 오류는 성공 응답 못지않게 명시적인 계약이 필요하다.

단계

1. /root/work/fa-problem-lab/service.py에서 DomainError(code, message)는 Exception 하위 클래스이며 .code에 code를 보관합니다. str(예외)는 message입니다.

처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.

mkdir -p /root/work/fa-problem-labtest -e /root/work/fa-problem-lab/service.py || cp /opt/fixtures/ten_labs/fa-problem-lab/service.py /root/work/fa-problem-lab/service.pycd /root/work/fa-problem-lab

2. /root/work/fa-problem-lab/service.py에서 status_for(code)는 missing=404, conflict=409, invalid=422, 그 외=500입니다.

3. /root/work/fa-problem-lab/service.py에서 public_message(code)는 missing='Resource not found', conflict='State conflict', invalid='Invalid request', 그 외='Internal error'입니다.

4. /root/work/fa-problem-lab/service.py에서 request_id(value)는 ASCII 영문·숫자·밑줄·하이픈으로만 이루어진 1~32자 문자열이면 그대로, 아니면 'untracked'입니다.

5. /root/work/fa-problem-lab/service.py에서 problem(code, rid)는 type='urn:labhub:problem:'+code, title와 detail=public_message(code), status=status_for(code), request_id=request_id(rid)만 가진 딕셔너리입니다.

6. /root/work/fa-problem-lab/service.py에서 response_for(code, rid)는 problem을 본문으로, status_for를 상태로, application/problem+json을 media_type으로, X-Request-ID를 정규화한 rid로 둔 JSONResponse입니다.

7. /root/work/fa-problem-lab/service.py에서 install_handlers(app)는 DomainError 핸들러를 등록합니다. 헤더 X-Request-ID를 읽고 exc.code에 대해 response_for를 반환합니다. exc의 message는 응답에 넣지 않습니다.

8. /root/work/fa-problem-lab/service.py에서 create_app()은 핸들러를 설치하고 GET /fail/{code}에서 DomainError(code, 내부문장)를 냅니다. 단 code=boom이면 RuntimeError를 냅니다. RuntimeError 핸들러는 code=internal인 고정 500 문제 응답을 반환합니다.

참고

8단계

  1. 업무 예외에 code를 남긴다
  2. code를 상태로 매핑한다
  3. 공개 문장을 고정한다
  4. 요청 id를 제한한다
  5. 문제 본문을 만든다
  6. 응답 형식을 일관되게 만든다
  7. 업무 예외 핸들러를 연결한다
  8. 예상하지 못한 오류도 숨긴다