倉庫に同じ注文が3回届いた
目標
顧客の合成した注文CSVを検証し、架空の倉庫に予約するPython CLIを実装します。 同じファイルの再実行と、応答の消失があっても、予約を重複して作らず、未解決の項目を引き継ぎます。
なぜ重要なのか
API呼び出しの成功と、顧客の業務の完了は違います。応答を受け取れなかったからといって、無条件に再送すると、すでに保存した注文をもう一度作ってしまうおそれがあります。 逆に、再試行をすべて禁止すると、保存の前に失敗した正常な注文を失います。このラボは、入力の契約・冪等キー・永続的な元帳・再試行の上限を、あわせて扱います。 実際の出荷・決済・顧客情報がない、架空のロボット部品の倉庫です。相手側の元帳ファイルを直接読んだり直したりせず、HTTPの契約だけで連携します。
想定所要時間は80分です。デフォルトの60分のセッションが終わる前に、+時間で延長してください(最大180分)。 セッションが終わると、/root/handoffのファイルは消えます。必要なコードは、終了の前に、個人の作業スペースに別に保管してください。
用意するものと実行契約
- 顧客のブリーフ: /opt/data/handoff-brief.md
- CLI・報告書・引き継ぎ文書の契約: /opt/data/handoff-program-contract.md
- 合成した注文: /opt/data/handoff-orders.csv
- スターターコード: /opt/data/handoff-sync-starter.py
- 実行の評価ツール: /opt/lab/handoff/evaluate.py
まず、ブリーフと実行契約を読みます。入力は最大256KiB・100行で、数量は1から5までの1文字です。 保留されていない注文だけを、APIの本文のorder_id・sku・quantityとして用意します。メールは、送信・ログ・報告書から除きます。 評価ツールには、プログラムのパスと、plan/send/chaos/repeat/drift/outageのうちの検査名を渡します。 例: python3 /opt/lab/handoff/evaluate.py /root/handoff/sync.py plan 評価ツールが、隔離された一時的なデータとサーバーを作って終了するので、別のサーバーを先に立ち上げる必要はありません。 評価データでは、注文番号・SKU・数量の一部が変わります。提供されたCSVの結果をハードコードしないでください。
ステップ
- /root/handoffを作成し、スターターコードをsync.pyにコピーします。scope.jsonに、業務warehouse-reservation、ビジネスキーorder_id、send_pii=false、max_attempts=4、retryable_statuses=[503]、conflict_policy=hold_order、approval=review_onlyを記録します。JSONのキー名は、workflow・business_key・send_pii・max_attempts・retryable_statuses・conflict_policy・approvalです。
- sync.pyに、CSV全体の検証と、計画の報告書を実装します。デフォルトのモードは、HTTPリクエスト0回で、outcomesは空の配列です。同じ注文の不良・衝突は注文全体を保留し、同じ内容は最初の行だけが候補で、残りはduplicatesです。planの検査で確認します。
- sync.pyの--sendの分岐を実装します。正常なサーバーで、候補だけをPOSTし、GET /orders/<注文番号>で、ちょうど1つの予約と、内容・IDを確認します。報告書の7つのフィールドと、outcomesの4つのフィールド、終了コード0/2は、実行契約に従います。sendの検査で、実際の元帳と照合します。
- sync.pyに、リクエストごとの1秒の制限、同じ注文のキー、503・接続エラーでの最大4回の試行と0.1秒の間隔を実装します。保存前の503と、保存後の応答の消失を、どちらも処理できるように、chaosの検査を通します。4xxは再試行しません。
- sync.pyが、次の実行でも同じビジネスキーを使うことを確認します。repeatの検査は、同じ元帳でサーバーを再起動し、同じ入力をもう一度実行します。新しい予約が増えたり、IDが変わったりしたら、実行のたびに変わるキーと、再試行の分岐を直します。
- sync.pyが、すでに予約された注文の変更後の数量を、新しいキーで迂回しないように実装します。driftの検査は、2回目の実行で、一部の数量を変えます。409は、POST 1回で止めて、rejected・nullとして残し、既存の予約を維持する必要があります。
- sync.pyが、持続する503で、注文ごとにPOSTを4回行ったあとで止まり、unconfirmed・nullを記録することを確認します。outageの検査を実行します。参照で確認できなかった結果も、成功として表示しません。
- 現在の実装で、6つの検査をすべて実行して、handoff.jsonを作ります。評価ツールのreceiptモードを使い、実装とサンプルのハッシュ、事例の一覧、review_onlyの決定、未解決の不良の数量・衝突した注文と、実験の限界を残します。最終的な採点は、scope.json・sync.py・handoff.jsonをあわせて読み、実行を再び検証します。
参考
- 計画モードの自己実行の例: python3 /root/handoff/sync.py --input /opt/data/handoff-orders.csv --base-url http://127.0.0.1:8031 --report /root/handoff/plan.json
- 上の計画モードは、サーバーに接続しません。自分で--sendを試すには、ブリーフにある架空のAPIの起動コマンドを、先に使ってください。
- 引き継ぎの生成: python3 /opt/lab/handoff/evaluate.py /root/handoff/sync.py receipt > /root/handoff/handoff.json
- 実装が変わったら、引き継ぎ文書も再検証して作り直します。ハッシュだけを変えないでください。
- 検証の成功は、運用の承認ではありません。単一の合成した倉庫であり、同じUIDの悪意のあるプログラムを防ぐ、別のセキュリティの境界でもありません。
顧客の言葉から作業範囲を固定する
ビジネスキーと、配信イベントを区別してください。衝突は、エンジニアが数量を選ぶ代わりに、注文全体を保留します。
送る前に7行を分類する
csv.DictReaderでファイル全体を読み、order_idごとにまとめてから分類してください。CSVパーサーのデフォルトのフィールドの制限も、入力の上限に合わせてください。7行目を送ったあとで8行目の衝突を見つけたのでは、手遅れです。
正常な予約を参照で確認する
POSTの応答と、GETの元帳のID・SKU・数量を照合してください。保留が残る送信結果は、終了コード2で表します。
保存前の失敗と応答の消失を復旧する
同じorder_idを冪等キーとして維持してください。エラーの種類、試行回数、業務上の確認の有無は、別々のことです。
次の担当者が同じファイルを再実行する
キーに、現在の時刻や実行のUUIDが混ざっていないかを見てください。既存の予約の数だけでなく、IDも同じである必要があります。
変わった数量を新しい注文として隠さない
本文のハッシュをキーにすると、数量が変わったときに新しい予約になります。409は、再試行やキーの交換ではなく、拒否の結果として残してください。
倉庫が不調のままのときに止める
最大の試行回数に達しても、成功の状態に変えないでください。確認されていない予約IDは、nullです。
実行の根拠と未解決の項目を引き継ぐ
receiptモードで、現在のコードを再検証してください。コードを直したなら、ハッシュだけを直さず、検証をもう一度実行します。