プロンプトとコンテキスト
あなたはチームプロフィール API を担当しています。クライアントは displayName のみを変更することも、完全なプロフィールを送信することもあり、モバイル環境でのリトライも頻繁に発生します。PUT と PATCH の使い分け、省略されたフィールドや null の定義方法、コンフリクト、アトミック性、冪等なリトライ、後方互換性の処理方法について説明してください。
Greenroom の 2026 年バックエンド面接リストには、PUT と PATCH の違い、冪等性、メソッドの選択が明示的に含まれています。RFC 5789 では、PUT エンティティをリソースの新しい完全な表現と定義し、PATCH エンティティを現在のリソースに適用する一連の指示と定義しています。この質問は特定の企業に限定されたものではありません。
面接官が評価するポイント
平均的な回答は「PUT は全体更新、PATCH は部分更新」と暗記した内容にとどまります。優れた回答では、リソースの規約を定義し、省略されたフィールドが未変更のまま維持されるかどうか、null がフィールドをクリアするかどうかを明示し、If-Match について説明し、リクエストが失敗した場合に変更全体が未適用のままになるかどうかを述べます。その後の追加質問では通常、重複リクエスト、レスポンスの消失、未知のフィールド、監査イベント、古いクライアントへの対応などが扱われます。
重要な評価シグナルは、PATCH を単なる「小さな PUT」として扱うのではなく、HTTP セマンティクスをデータベースの更新や並行性制御と適切に結びつけているかという点です。
確認すべき質問
- PUT でリソースを作成できますか? このプロンプトは既存プロフィールの更新を対象としています。固定 URI での作成を許可する場合は、所有権と重複リクエスト時の挙動を定義してください。
- クライアントは完全なリソースを送信しますか、それとも変更ドキュメントを送信しますか? 完全な表現には PUT を使用し、フィールド操作や部分的な表現には PATCH を使用します。
- 省略と
nullは何を意味しますか? ここでは、省略は値を維持することを意味し、nullは null 許容フィールドをクリアすることを意味します。null 非許容フィールドの場合は拒絶されます。 - 並行更新によって互いのデータが上書きされる可能性はありますか? このプロンプトでは暗黙的な上書きを認めず、
ETag/If-Matchまたはデータベースのバージョン条件を必須とします。 - 副作用は存在しますか? 検索インデックスの更新、監査イベント、通知はコミットされた状態に追従する必要があります。非同期の副作用は HTTP のアトミック性の一部ではありません。
30秒の回答
「私は PUT を、完全なスナップショットを保持しているクライアント向けにリソースを完全な表現で置き換えるものとして定義します。PATCH は displayName の更新のような部分的な変更を伝達します。PATCH では省略と null の意味を明確に定義し、古いフォームが新しいデータを上書きしないよう、古いバージョンを拒絶する必要があります。サーバーはドキュメント全体を検証して単一のデータベーストランザクション内でアトミックに適用し、バージョン付きの非同期イベントを発行します。クライアントは同一のリクエストセマンティクスと If-Match を使用してリトライします。変更がリソース更新ではなくコマンドである場合は、PATCH を多重定義せずアクションエンドポイントを使用します。」
ステップ・バイ・ステップの回答
ステップ 1: 両メソッドをリソース規約として定義する
| 観点 | PUT | PATCH |
|---|---|---|
| リクエストの意味 | ボディはリソースの新しい完全な表現である | ボディは現在のリソースに適用される変更指示または部分的な表現である |
| 省略されたフィールド | 通常、クライアントが完全な状態を提供したことを意味し、古いフィールドを暗黙的に維持してはならない | 維持するか無効であるかを明示的に定義しなければならない |
| 冪等性 | 同じ表現を繰り返しても同一のリソース状態に到達するべきである | メソッド自体では保証されないが、特定のパッチドキュメントは冪等になり得る |
| 一般的な用途 | エディタの完全なスナップショットの同期や設定の置換 | JSON Merge Patch や JSON Patch による単一フィールドの変更 |
PUT の重要な特性はボディのサイズではなく、その表現が完全であるというクライアントの主張です。クライアントが一部のフィールドしか把握していないにもかかわらず PUT を送信した場合、サーバーは欠落したフィールドを削除またはデフォルト値への変更と解釈する可能性があります。PATCH も自動的に安全になるわけではありません。バリデーション、認可、副作用の制御は依然として必要です。
ステップ 2: PATCH ドキュメントと3つのフィールド状態を定義する
PATCH /v1/teams/t_123/profile HTTP/1.1
Content-Type: application/merge-patch+json
If-Match: "profile-v17"
{"displayName":"Design Platform","avatarUrl":null}このプロンプトでは JSON Merge Patch 形式を採用しています。displayName は置換され、avatarUrl: null は null 許容フィールドをクリアし、省略されたフィールドは変更されずに維持されます。ビジネス要件として要素単位の配列操作、移動、テストが必要な場合は、制限付きの JSON Patch 操作リストを使用します。
まずドキュメントをパースし、次に許可リスト、型、長さ、認可、ドメインの不変条件を検証します。クライアントからの任意の JSON パスをデータベースのカラムに直接マッピングしてはなりません。未知のフィールドは、バージョン管理された規約内で拒絶するか無視するかのいずれかを選択できますが、その動作は確定し文書化されている必要があります。
ステップ 3: バージョン条件によって暗黙的な上書きを防ぐ
GET /v1/teams/t_123/profile HTTP/1.1
ETag: "profile-v17"
PATCH /v1/teams/t_123/profile HTTP/1.1
If-Match: "profile-v17"
Content-Type: application/merge-patch+json
{"displayName":"Design Platform"}データベースの更新にはバージョン述語を含めます。バージョン 17 のみが書き込み可能であり、18 へと進めることができます。不一致の場合は 412 Precondition Failed が返され、クライアントは再読み込み、コンフリクトの表示、またはパッチの再生成を行います。これにより、古いバージョンが新しいデータを強制的に上書きすることを防ぎます。If-Match が必須であるにもかかわらず欠落している場合は、428 Precondition Required を使用して並行性ポリシーを明示できます。
PATCH はドキュメントとしてもアトミックである必要があります。フィールド A の更新に成功しながらフィールド B で失敗するという中途半端な更新は許容されません。トランザクション、検証フェーズ、ユニーク制約によって変更をコミットするかどうかが決定されます。副作用を伴うイベントはアウトボックスに書き込み、コミット後にリソースバージョンとともに発行する必要があります。
ステップ 4: 重複リクエストと結果が不明な状態を処理する
同一の完全な表現を持つ PUT リクエストを繰り返すと、同じ状態に収束します。PATCH がこの特性を持つのは、操作が反復可能な場合に限られます。displayName の設定は冪等ですが、increment seats by 1 は冪等ではありません。非冪等な PATCH には、リクエスト ID、バージョン条件、または目標状態を表現する操作が必要です。
ネットワーク障害の後、クライアントはサーバー側でコミットされたかどうかを把握できません。記録されたフィンガープリントと結果を持つ安定したリクエスト ID を使用するか、If-Match と目標状態を指定してリトライします。副作用を追加するパッチを盲目的に再実行してはなりません。リソースがコミットされた後、イベントコンシューマーがインデックス作成や通知を処理し、イベント ID で重複を排除します。
ステップ 5: PUT/PATCH が不適切なケースを判断する
「プロフィールの公開」「権限の再計算」「招待の送信」はコマンドであり、置換や部分的なリソース表現ではありません。POST /profile:publish のようなアクションエンドポイントを使用する方が、権限、監査、リトライ、非同期状態をより明確に表現できます。PATCH 形式のコマンドを使用すると、クライアントが重複実行や副作用を誤認する原因になります。
競合が激しい場合やリソース間のトランザクションを伴う場合も、ドメインコマンドが必要になることがあります。メンバーを編集者からオーナーに変更する操作にはクォータや監査のチェックが必要ですが、PATCH で単一の文字列を変更するだけではこれらの不変条件を表現できません。
質の高い模範解答
「私は PUT と PATCH を異なる規約として公開します。PUT はチームプロフィールの完全な表現を受け入れます。省略されたフィールドは規約エラーまたは明示的なデフォルト値として扱われ、誤って『未変更のまま維持』されることはありません。PATCH は制限付きの Merge Patch を受け入れ、許可リストに登録されたフィールドのみを対象とします。省略は値を維持し、null はそのフィールドが null 許容である場合にのみクリアします。
両メソッドともに ETag と If-Match を使用します。データベースはバージョン条件付きの更新を実行し、不一致時には 412 を返すため、古いクライアントが新しいデータを上書きすることはできません。PATCH ドキュメントは完全に検証され、単一のトランザクションで適用されます。コミット後にはアウトボックスからバージョン付きのインデックスイベントが発行されます。フィールドを設定する PATCH は冪等になり得ますが、インクリメント操作にはリクエスト ID、条件、または目標状態の指定が必要です。公開や招待は明示的な副作用を持つため、POST アクションとします。テストでは、重複、レスポンス消失、省略/null、コンフリクト、未知のフィールド、部分的な失敗、古いクライアントとの互換性を検証します。」
よくある間違い
- 症状 → PUT を「任意のフィールドの更新」と呼ぶ → 失敗の原因 → 呼び出し元が、省略されたフィールドが消えるのかどうかを把握できない → 修正方法 → PUT を完全な表現とし、部分的な変更には PATCH を使用する。
- 症状 → PATCH は本質的に冪等であると主張する → 失敗の原因 → RFC 5789 では保証されておらず、インクリメントの繰り返しによって状態が変化する → 修正方法 → 目標状態を指定するパッチのみを反復可能とし、その他の操作には条件や重複排除を追加する。
- 症状 → 任意の JSON キーをカラムに直接マッピングする → 失敗の原因 → フィールドの認可、型の検証、複数フィールド間の不変条件がバイパスされる → 修正方法 → 許可リストと明示的なドメイン検証を使用する。
- 症状 → バージョンコンフリクト発生時に Last-Writer-Wins(最後の書き込み優先)にする → 失敗の原因 → 古いフォームが新しいデータを暗黙的に上書きしてしまう → 修正方法 →
If-Matchとバージョン述語を使用し、コンフリクト時には 412 を返す。 - 症状 → コミット後に検索サービスを同期的に呼び出す → 失敗の原因 → レスポンスの消失やプロセスのクラッシュにより、リソースとインデックスの間で不整合が発生する → 修正方法 → トランザクション内でアウトボックスに書き込み、イベント ID による重複排除を行いながら非同期に発行する。
追加の質問と回答
プロダクトの要件として、PATCH で省略されたフィールドの値をクリアしたい場合はどうしますか?
それは PATCH をもう一つの完全表現規約に変えてしまい、PUT との境界を曖昧にします。PUT を使用するか、フィールドセット置換用のメディアタイプを明確に定義して名前を付けるべきです。重要なのは、フィールドを省略した場合の結果を明示することです。
2つのクライアントが両方ともバージョン 17 を読み込み、それぞれ異なるフィールドを編集した場合はどうなりますか?
サーバー側でそれらの編集が独立していると仮定することはできないため、デフォルトでは 412 を返し、クライアント側でマージしてリトライさせます。フィールドレベルのマージが安全に行えるのは、ドメインがそれを明示的に許可し、パッチにフィールドバージョンやテスト操作が含まれている場合に限られます。
PATCH が課金や通知をトリガーする場合はどうしますか?
リソースの更新と副作用を監査可能なステップに分離します。リソースとアウトボックスを同時にコミットし、冪等なコンシューマーにイベントを処理させます。副作用にユーザーの明示的な意図が必要な場合は、非同期操作ステータスを持つ個別の POST コマンドに切り替えます。