TT Lab
料金プラン はじめる
学ぶ 学習パス コース

本番バックエンドAPIキャップストーン

キャップストーン: 組織境界を守る冪等な注文API

TT Labで続きを見る

目標

注文API1つを、本番の責任まで付けて完成させます。データ契約、トランザクションの 冪等性、組織境界の認可、実行されるHTTP、3つの観測シグナル、安全なデプロイオブジェクト、 そして他人の手に渡せるドキュメントまでを、1つのプロジェクトとして結びます。

8つのステップが、それぞれ異なる採点の軸(正確性・セキュリティ・問題解決・運用・ドキュメント)を見ます。

時間と保存に関する警告: 先に読んでください

そのため、一度に座って終えることを前提に設計されています。時間が足りなそうなら、 ステップ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 스키마

このコードブロックの韓国語コメントは、各ファイルの説明で、app.pyはpsycopgで接続して--host/--portで起動すること、migrationsのSQLはBEGIN/COMMITで囲んだ順方向のマイグレーションであること、テストは再試行と別組織の反例を動作で検証すること、deployment.yamlはJSONとしても読めるYAMLであること、portfolio.jsonはbackend-capstone-v2スキーマであることを述べています。

すべてのステップで最初にかかる検査

採点ツールは、ステップの検査の前に、プロジェクト全体をなめて秘密を探します。1つでも 見つかれば、どのステップを採点しても同じ文言で失敗するので、原因をそのステップで 探して時間を無駄にしがちです。

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

このコードブロックの韓国語コメントは、左の列が検出対象の種類で、パスワードを含む接続文字列、クラスター内部のアドレス、Bearerトークンの原文、password・api_key・secretへの代入、という意味です。

接続情報は環境変数で受け取り、例のトークンは<token>のように、プレースホルダーとして書いてください。 サイズの制限も併せて見ます。ファイル4,096個・1ファイル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に、非root、seccompProfile: RuntimeDefault、 allowPrivilegeEscalation: false、読み取り専用のルート、requests/limits、 2つのプローブ、secretKeyRef、そしてダイジェストで固定したイメージを入れます。
  7. ドキュメント: ADRとインシデントレポート。インシデントレポートには、措置ごとに担当者と期限を書きます。
  8. ポートフォリオ: portfolio.jsonに、許可された5つの要約だけを入れます。 元のヘッダー・トークン・プライベート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の契約を実装してください。

3つの観測シグナルをリクエストに結びつける

観測: 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に、非root、seccompProfile: RuntimeDefault、 allowPrivilegeEscalation: false、読み取り専用のルート、requests/limits、 2つのプローブ、secretKeyRef、そしてダイジェストで固定したイメージを入れます。

deploy/deployment.yamlは、JSON互換のYAMLで書いてください。非root/seccomp/権限昇格の禁止/読み取り専用のルート、リソース、2つのプローブ、Secretの参照、digestのイメージを、正確なオブジェクトのパスに置きます。

ADRとインシデントからの学びを結びつける

ドキュメント: ADRとインシデントレポート。インシデントレポートには、措置ごとに担当者と期限を書きます。

ADRにstatus/context/alternatives/decision/consequencesを、インシデントレポートにimpact/timeline/root cause/detection/corrective actionsと、担当者・期限を書いて、秘密と内部アドレスは取り除いてください。

許可リストのポートフォリオを発行する

ポートフォリオ: portfolio.jsonに、許可された5つの要約だけを入れます。 元のヘッダー・トークン・プライベートIP・内部DNS・ターミナルの記録は入れません。

portfolio.jsonをbackend-capstone-v2に固定して、test_summary、metric_names、deployment_summary、adr_summary、incident_summaryだけを入れてください。元のヘッダー・トークン・プライベートIP・内部DNS・ターミナルのログは禁止です。