ETagで更新の上書き競合を防ぐAPI:設計の考え方
一言でいうと
「自分が読んだバージョンのときだけ更新する」という条件を、DBの書き込み文まで伝えてはじめて、更新の消失を防げます。
なぜ必要なのか
AとBが、同じメモv1を読みました。Aがタイトルを更新したあとで、Bが古い画面から保存すると、条件のないUPDATEは、Aの変更を黙って上書きします。保存の直前にSELECTでバージョンを確認しても、確認とUPDATEの間に、ほかのリクエストが割り込むことがあります。比較と更新は、1つのSQL文にまとめ、変更された行数を確認する必要があります。ここでは、SQLiteのファイルを実際に開いて、別々の接続の2つのリクエストを競合させます。プロセスのメモリ上の辞書だけを検査することはしません。
どう動くのか
GET → ETag: "v1"
├─ A: PUT If-Match "v1" → UPDATE WHERE version=1 → 204, version=2
└─ B: PUT If-Match "v1" → 변경 행 0 → 412
ETagは、表現のバリデーターです。このラボでは、idごとのtitleが変わるときにversionがちょうど1増え、レスポンスの表現のほかの要素は変わらないという制限のもとで、強いタグを作ります。実際のサービスで、表現の圧縮・言語・権限ごとのフィールドが異なる場合は、同じバージョンの文字列を無条件に再利用してはいけません。
現場での姿
If-Matchは、強い比較を使います。この教育用のAPIは、単一のタグだけを受け取り、weakタグ、ワイルドカード、リストには対応していません。これはHTTP全体の文法を実装したものではなく、APIに明示した狭い契約です。存在しないメモは404、条件の欠落は428、対応していない条件や古いバージョンは412と決めます。クライアントは、412を無限にリトライせず、最新のメモを読んで、ユーザーにマージを求める必要があります。入力エラーである空のタイトルは、422として区別します。
次のラボですること
スキーマ → 作成 → 取得 → タグ → 条件の解釈 → アトミックな更新 → 結果コード → FastAPIの順に完成させます。元のタイトルが保存されているかも確認します。412という数字だけが合っていて、あとでUPDATEをしてしまう実装は、通ってはいけません。DB接続は、作業ごとに閉じて、テスト間の状態のリークを防ぎます。
参考: HTTPの条件付きリクエスト