TT Lab
요금제 시작하기
배우기 러닝패스 코스

프로덕션 백엔드 API 캡스톤

캡스톤: 조직 경계를 지키는 멱등 주문 API

TT Lab 에서 이어서 보기

목표

주문 API 하나를 프로덕션 책임까지 붙여서 완성합니다. 데이터 계약, 트랜잭션 멱등성, 조직 경계 인가, 실행되는 HTTP, 세 관측 신호, 안전한 배포 객체, 그리고 남의 손에 넘길 수 있는 문서까지 한 프로젝트로 잇습니다.

여덟 단계가 각각 다른 채점 축(정확성·보안·문제해결·운영·문서)을 봅니다.

시간과 저장에 대한 경고 — 먼저 읽으세요

그래서 한 번에 앉아서 끝내는 것을 전제로 설계돼 있습니다. 시간이 모자랄 것 같으면 8단계까지 얕게라도 한 바퀴 돌아 두는 편이, 3단계까지 깊게 하다 잃는 것보다 낫습니다. 중간에 나가야 한다면 프로젝트 폴더를 tar 로 묶어 손으로 내려받아 두세요.

환경

PostgreSQL 16 이 이미 떠 있습니다.

postgresql://lab@127.0.0.1:5432/labdb

프로젝트 뿌리는 /root/work/backend-capstone 이고, 채점기는 이 경로만 봅니다. 먼저 만들어 두세요.

mkdir -p /root/work/backend-capstone
cd /root/work/backend-capstone
mkdir -p migrations tests docs deploy

만들 파일

app.py                          psycopg 로 붙고 --host/--port 로 뜬다
migrations/001_create_orders.sql  BEGIN/COMMIT 으로 감싼 순방향 마이그레이션
tests/test_integration.py       재시도와 다른 조직 반례를 행동으로 검증
deploy/deployment.yaml          JSON 으로도 읽히는 YAML
docs/ADR-001.md                 status/context/alternatives/decision/consequences
docs/incident-report.md         impact/timeline/root cause/detection/corrective actions
portfolio.json                  backend-capstone-v2 스키마

모든 단계에 먼저 걸리는 검사 하나

채점기는 단계 검사 전에 프로젝트 전체를 훑어 비밀을 찾습니다. 하나라도 걸리면 어느 단계를 채점하든 똑같은 문구로 실패하므로, 원인을 그 단계에서 찾다가 시간을 버리기 쉽습니다.

비밀번호가 들어간 접속 문자열   postgresql://lab:1234@...
클러스터 내부 주소             *.svc.cluster.local
Bearer 토큰 원문               Authorization: Bearer eyJ...
password/api_key/secret 대입   password = "hunter2"
개인 키                        -----BEGIN PRIVATE KEY-----

접속 정보는 환경변수로 받고, 예제 토큰은 <token> 처럼 자리표시자로 쓰세요. 크기 제한도 함께 봅니다 — 파일 4,096개 · 한 파일 1MB · 합계 8MB 안쪽입니다. .git 과 __pycache__ 는 세지 않습니다.

단계

  1. 데이터 계약 — orders 표에 identity PK, amount_cents > 0 검사, owner_id·org_id, created_at timestamptz, unique(org_id, idempotency_key), 그리고 (org_id, owner_id) 조회 인덱스. 전체를 BEGIN;/COMMIT; 으로 감쌉니다.
  2. 트랜잭션 멱등성 — ON CONFLICT (org_id, idempotency_key) 로 재시도를 흡수하고, 예외에서 롤백합니다. 값은 반드시 매개변수로 바인딩합니다.
  3. 인가 — authenticate 는 신원을 만들고, authorize(identity, order) 는 조직을 먼저 본 뒤 소유자·관리자를 봅니다. 이 파일은 import 만 해도 아무것도 출력하지 않아야 합니다.
  4. HTTP — python3 app.py --host 127.0.0.1 --port <포트> 로 떠야 합니다. POST /orders · GET /orders/{id} · POST /orders/{id}/cancel · /healthz, 그리고 모든 응답에 X-Trace-Id. 상태 코드는 201/200/400/401/403 을 구분합니다.
  5. 관측 — orders_created_total, http_request_duration_seconds, 그리고 event·route·status·duration_ms·trace_id 를 담은 JSON 로그. authorization 헤더와 내부 DNS 는 남기지 않습니다.
  6. 배포 — deploy/deployment.yaml 에 비루트, seccompProfile: RuntimeDefault, allowPrivilegeEscalation: false, 읽기 전용 루트, requests/limits, 두 프로브, secretKeyRef, 그리고 다이제스트로 고정한 이미지.
  7. 문서 — ADR 과 사고 보고서. 사고 보고서에는 조치마다 담당자와 기한을 적습니다.
  8. 포트폴리오 — portfolio.json 에 허용된 다섯 요약만 넣습니다. 원본 헤더·토큰·사설 IP·내부 DNS·터미널 기록은 넣지 않습니다.

참고

3단계의 "import 해도 조용해야 한다" 는 조건이 매번 걸리는 자리입니다. 모듈 맨 위에서 데이터베이스에 붙거나 print 를 하면 채점기가 그 출력을 봅니다. 붙는 일은 main() 안으로, 진단은 로거로 옮기세요.

PostgreSQL 16 스키마를 실행한다

데이터 계약 — orders 표에 identity PK, amount_cents > 0 검사, owner_id·org_id, created_at timestamptz, unique(org_id, idempotency_key), 그리고 (org_id, owner_id) 조회 인덱스. 전체를 BEGIN;/COMMIT; 으로 감쌉니다.

migrations/001_create_orders.sql에 identity PK, 양수 금액, owner/org, 조직 범위 멱등 UNIQUE, TIMESTAMPTZ와 조회 인덱스를 만들고 트랜잭션으로 감싸세요. app.py는 psycopg를 사용해야 합니다.

트랜잭션 멱등 생성을 구현한다

트랜잭션 멱등성 — ON CONFLICT (org_id, idempotency_key) 로 재시도를 흡수하고, 예외에서 롤백합니다. 값은 반드시 매개변수로 바인딩합니다.

psycopg transaction/cursor와 매개변수 바인딩, ON CONFLICT (org_id, idempotency_key)를 사용하고 예외 시 rollback하세요. 재시도와 다른 조직 반례를 행동 테스트로 작성하세요.

인증과 소유자·조직 인가를 분리한다

인가 — authenticate 는 신원을 만들고, authorize(identity, order) 는 조직을 먼저 본 뒤 소유자·관리자를 봅니다. 이 파일은 import 만 해도 아무것도 출력하지 않아야 합니다.

authenticate는 Bearer 신원을 만들고 authorize(identity, order)는 조직을 먼저, 그다음 owner/admin 관계를 판단하세요. import와 오류 출력에 토큰을 남기지 마세요.

실제 HTTP API 계약을 연다

HTTP — python3 app.py --host 127.0.0.1 --port <포트> 로 떠야 합니다. POST /orders · GET /orders/{id} · POST /orders/{id}/cancel · /healthz, 그리고 모든 응답에 X-Trace-Id. 상태 코드는 201/200/400/401/403 을 구분합니다.

표준 라이브러리 또는 설치된 런타임으로 app.py --host ... --port ...를 지원하세요. POST /orders, GET /orders/id, POST /orders/id/cancel, health/readiness와 상태·JSON·X-Trace-Id 계약을 구현하세요.

세 관측 신호를 요청에 연결한다

관측 — orders_created_total, http_request_duration_seconds, 그리고 event·route·status·duration_ms·trace_id 를 담은 JSON 로그. authorization 헤더와 내부 DNS 는 남기지 않습니다.

orders_created_total, http_request_duration_seconds, JSON 로그의 event/route/status/duration_ms와 응답 trace ID를 연결하세요. Authorization과 내부 DNS는 기록하지 마세요.

파싱 가능한 안전한 배포 객체를 만든다

배포 — deploy/deployment.yaml 에 비루트, seccompProfile: RuntimeDefault, allowPrivilegeEscalation: false, 읽기 전용 루트, requests/limits, 두 프로브, secretKeyRef, 그리고 다이제스트로 고정한 이미지.

deploy/deployment.yaml은 JSON-compatible YAML로 작성하세요. 비루트/seccomp/권한 상승 금지/읽기 전용 루트, 리소스, 두 프로브, Secret 참조, digest 이미지를 정확한 객체 경로에 둡니다.

ADR과 사고 학습을 연결한다

문서 — ADR 과 사고 보고서. 사고 보고서에는 조치마다 담당자와 기한을 적습니다.

ADR에 status/context/alternatives/decision/consequences를, 사고 보고서에 impact/timeline/root cause/detection/corrective actions와 담당자·기한을 쓰고 비밀과 내부 주소는 제거하세요.

허용 목록 포트폴리오를 발행한다

포트폴리오 — portfolio.json 에 허용된 다섯 요약만 넣습니다. 원본 헤더·토큰·사설 IP·내부 DNS·터미널 기록은 넣지 않습니다.

portfolio.json을 backend-capstone-v2로 고정하고 test_summary, metric_names, deployment_summary, adr_summary, incident_summary만 넣으세요. 원본 헤더·토큰·사설 IP·내부 DNS·터미널 로그는 금지합니다.