ETagで更新の上書き競合を防ぐAPI
目標
2人のユーザーが同じ下書きを更新するときに、SQLの条件付き更新とHTTPのレスポンスで、消失を防ぎます。
なぜ重要なのか
正常なリクエストが1回成功しても、境界値や障害からの復旧は保証されません。このラボは、各関数の契約を小さく実装またはテストしたあと、実際の実行につなげます。コードが存在するかどうかやレポートの文言だけを見るのではなく、結果・例外・保存された状態を検査します。前のステップのコードを維持しながら、次のステップへ進んでください。
ステップ
/root/work/fa-conditional-lab/service.pyで、init_db(path)は、SQLiteのファイルにnotes(id TEXT PRIMARY KEY, title TEXT NOT NULL, version INTEGER NOT NULL)を作成してください。繰り返し呼び出しても、既存のデータを削除しません。最初の準備は、次のコマンドで行います。
mkdir -p /root/work/fa-conditional-lab
cp /opt/fixtures/practice_depth/fa-conditional-lab/* /root/work/fa-conditional-lab/
cd /root/work/fa-conditional-lab
/root/work/fa-conditional-lab/service.pyで、create_note(path, note_id, title)は、バージョン1でメモをINSERTしてコミットしてください。同じidはsqlite3.IntegrityErrorで拒否し、既存のタイトルを上書きしません。/root/work/fa-conditional-lab/service.pyで、read_note(path, note_id)は、{id, title, version}の辞書またはNoneを返してください。SQLにidを文字列連結せず、バインドします。/root/work/fa-conditional-lab/service.pyで、etag(version)は、整数のバージョンを、二重引用符まで含めた文字列で返してください。例: バージョン3は、文字列'"v3"'です。/root/work/fa-conditional-lab/service.pyで、parse_match(header)は、二重引用符の中がvと正の10進数の整数1つだけのタグだけを受け取って、バージョンを返してください。None、weakタグ、ワイルドカード、リスト、v0などは、ValueErrorです。例: '"v12"' → 12。/root/work/fa-conditional-lab/service.pyで、update_note(path, note_id, title, version)は、idとversionがどちらも一致したときだけ、タイトルを更新して、バージョンを1上げてください。変更された行が1つならTrue、なければFalseです。同時のリクエストでも、勝者は1つでなければなりません。/root/work/fa-conditional-lab/service.pyで、put_note(path, note_id, title, header)は、存在しないidには404、条件の欠落には428、対応していない条件・バージョンの不一致には412、成功には204を返してください。拒否するときは、DBを変更しません。/root/work/fa-conditional-lab/service.pyで、create_app(path)は、GET /notes/{note_id}とPUT /notes/{note_id}を提供してください。GETはメモのJSONとETagを、PUTは{title: 1から100文字の文字列}とIf-Matchを受け取って、上の結果コードで空のレスポンスを返します。空のタイトルは422、存在しないGETは404です。
参考
- パッケージはイメージにインストールされています。インターネット接続やpip installは必要ありません。
- 準備のコピーは、最初の1回だけ行ってください。もう一度コピーすると、作業ファイルが初期化されます。
- 採点は、皆さんのservice.pyを読み込んで、別のidと一時的なストレージで実行します。結果を定数でハードコードしないでください。
- 各ステップの採点は、45秒以内で実行されます。無限ループや、実際の待機を作らないでください。
バージョンを持つストアを作る
/root/work/fa-conditional-lab/service.pyで、init_db(path)は、SQLiteのファイルにnotes(id TEXT PRIMARY KEY, title TEXT NOT NULL, version INTEGER NOT NULL)を作成してください。繰り返し呼び出しても、既存のデータを削除しません。最初の準備は、次のコマンドで行います。
mkdir -p /root/work/fa-conditional-lab
cp /opt/fixtures/practice_depth/fa-conditional-lab/* /root/work/fa-conditional-lab/
cd /root/work/fa-conditional-lab
CREATE TABLE IF NOT EXISTSを使い、with sqlite3.connect(path)でコミットの境界を決めます。
採点は、bash /opt/lab/checks/fa-conditional-lab/01-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
最初の保存のバージョンを固定する
/root/work/fa-conditional-lab/service.pyで、create_note(path, note_id, title)は、バージョン1でメモをINSERTしてコミットしてください。同じidはsqlite3.IntegrityErrorで拒否し、既存のタイトルを上書きしません。
INSERT OR REPLACEは、重複した作成を更新に変えてしまいます。このステップの契約は、新しいメモの作成です。
採点は、bash /opt/lab/checks/fa-conditional-lab/02-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
存在しないメモと存在するメモを区別する
/root/work/fa-conditional-lab/service.pyで、read_note(path, note_id)は、{id, title, version}の辞書またはNoneを返してください。SQLにidを文字列連結せず、バインドします。
fetchoneがNoneかどうかを、先に確認します。タプルのまま返さないでください。
採点は、bash /opt/lab/checks/fa-conditional-lab/03-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
強いETagを作る
/root/work/fa-conditional-lab/service.pyで、etag(version)は、整数のバージョンを、二重引用符まで含めた文字列で返してください。例: バージョン3は、文字列'"v3"'です。
HTTPのタグの二重引用符は、説明用の表記ではなく、実際のヘッダーの値の一部です。
採点は、bash /opt/lab/checks/fa-conditional-lab/04-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
対応している条件の文法だけを受け付ける
/root/work/fa-conditional-lab/service.pyで、parse_match(header)は、二重引用符の中がvと正の10進数の整数1つだけのタグだけを受け取って、バージョンを返してください。None、weakタグ、ワイルドカード、リスト、v0などは、ValueErrorです。例: '"v12"' → 12。
re.fullmatchを使うと、タグのあとに余分な文字列があっても見過ごされません。このラボは、HTTPのタグ全体の文法の実装ではありません。
採点は、bash /opt/lab/checks/fa-conditional-lab/05-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
比較と書き込みを1つのSQLにまとめる
/root/work/fa-conditional-lab/service.pyで、update_note(path, note_id, title, version)は、idとversionがどちらも一致したときだけ、タイトルを更新して、バージョンを1上げてください。変更された行が1つならTrue、なければFalseです。同時のリクエストでも、勝者は1つでなければなりません。
UPDATE ... WHERE id=? AND version=?のrowcountを見ます。SELECTのあとに無条件でUPDATEすると、競合を防げません。
採点は、bash /opt/lab/checks/fa-conditional-lab/06-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
失敗をHTTPの結果に分類する
/root/work/fa-conditional-lab/service.pyで、put_note(path, note_id, title, header)は、存在しないidには404、条件の欠落には428、対応していない条件・バージョンの不一致には412、成功には204を返してください。拒否するときは、DBを変更しません。
存在しないリソースの判定を先に行ってから、条件を解釈します。再取得で、失敗を成功のように変えてはいけません。
採点は、bash /opt/lab/checks/fa-conditional-lab/07-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
古い画面の保存を、HTTPで再現する
/root/work/fa-conditional-lab/service.pyで、create_app(path)は、GET /notes/{note_id}とPUT /notes/{note_id}を提供してください。GETはメモのJSONとETagを、PUTは{title: 1から100文字の文字列}とIf-Matchを受け取って、上の結果コードで空のレスポンスを返します。空のタイトルは422、存在しないGETは404です。
HeaderでIf-Matchを受け取り、TestClientでGETしたあと、同じタグを2回送ってみてください。最初の更新だけが適用され、新しいGETはv2になっている必要があります。
採点は、bash /opt/lab/checks/fa-conditional-lab/08-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。