設問と適用場面
公開Orders REST APIが、強制アップグレードできない600のサードパーティ連携および複数のモバイルアプリで既に使用されています。既存のGET /v1/ordersは、1つのレスポンスですべての注文を返します。各注文にはcustomer_name文字列が含まれており、statusは現在pendingまたはpaidのみを返します。次のリリースでは、顧客データを構造化オブジェクトとして公開し、リストをページネーション化し、refundedステータスを導入する必要があります。
600の連携、現在のフィールド、およびステータス値は面接上の前提条件です。拘束力のある制約は、すべてのコンシューマーがいつアップグレードするかをプロバイダー側で制御できないことです。達成すべき成功基準は、機能するv2の提供、元の規約(コントラクト)に基づくv1の動作継続、可観測性のある移行、検出可能な非推奨日の提示、およびバージョンの約束された意味を変更することのないリリース失敗からの復旧です。
これは、APIコントラクト、サーバー側の表現、リリースエンジニアリング、および互換性ガバナンスをテストするため、バックエンドの設問となります。注文システム全体の設計やデータベースのシャーディング計画は求められていません。各変更を分類する前に、既存の規約を書き出してください。単に「URLにv2を入れる」と言うだけでは、古いクライアントが安全なままであることの証明にはなりません。
{
"orders": [
{
"id": "ord_1",
"customer_name": "Ada Lovelace",
"status": "paid"
}
]
}面接官が評価するポイント
第一の評価シグナルは、候補者が3種類の互換性を区別しているかどうかです。ソース互換性(Source compatibility)は、SDKを再生成またはアップグレードした後も古いクライアントコードがコンパイルできるかを問います。ワイヤ互換性(Wire compatibility)は、古いシリアライザーが新しいメッセージをパースできるかを問います。セマンティック互換性(Semantic compatibility)は、同じ呼び出しが妥当なコンシューマーの期待通りに動作し続けるかを問います。フィールドの型が変わらないことだけでセマンティクスが保たれるわけではありません。「すべての注文を返す」から「最初の100件の注文を返す」へと暗黙的に変更した場合、有効なJSONは生成され続けますが、古いクライアント側ではデータ欠落が発生します。
第二の評価シグナルは、規約に基づいた判断力です。暗記された汎用テーブルでは、あらゆる実際のクライアントのパース動作を表現しきれません。省略時に以前の動作が維持される新しいオプショナルなリクエストフィールドは、通常v1のままで問題ありません。フィールドの削除、リネーム、または型の変更はv1を破壊します。追加のレスポンスフィールドは、規約で未知のフィールドが許可されており、実際のSDKがそれらを無視する場合にのみ安全な追加となります。レスポンスのenum値の追加には特別な精査が必要です。オープンenumの規約であれば拡張を許容できますが、クローズドenum、自動生成された静的型付けSDK、またはdefault節のない網羅的switch文は失敗する可能性があります。
第三の評価シグナルは、バージョン、実装、およびライフサイクルを結びつける実行可能な道筋です。優れた回答では、v1のプレゼンテーション層を維持し、共通のドメインロジックを使用して個別のv1およびv2レスポンスを生成し、リリース前にコントラクト差分テストや古いSDKテストを実行し、リリース後にコンシューマーごとの採用率、エラー、レイテンシを測定し、最終的には標準化された非推奨シグナル、移行ガイド、明確なシャットダウンゲートを設けて古いバージョンを廃止します。バージョン識別子はルーティングの選択肢に過ぎず、これらのタスクのいずれも自動的には実行しません。
回答前に明確にすべき質問
- コンシューマーは制御可能か? アトミックなロールアウトを調整できる社内の3つのサービスであれば、長期にわたるv2を持たずにexpand–migrate–contract(拡張–移行–縮小)パターンを使用できます。サードパーティや古いモバイルクライアントには、安定したバージョン境界と公開ライフサイクルが必要です。
- 現在の規約は、クライアントが未知のフィールドやenum値を無視することを要求しているか? これにより、追加されたレスポンスフィールドやenum値がv1にとどまれるかが決まります。有効なJSONであることだけでは不十分です。OpenAPIコントラクト、SDKの型、および実際のコンシューマーの動作を検証してください。
- リストのサイズはどの程度で、どのような信頼性リスクをもたらしているか? すべての注文を返してもSLOを満たせる場合、ページネーションはv2のみに存在させることができます。無制限のレスポンスが既に可用性を脅かしている場合は、レート制限と緊急の連絡経路が必要になる可能性がありますが、暗黙的な切り捨ては依然として互換性のある変更ではありません。
- APIは既にバージョン選択メカニズムを公開しているか? パスバージョンが既に存在する場合は
/v1および/v2を引き続き使用し、バージョンがヘッダーベースの場合は既存の日付ヘッダーを維持します。移行中にメカニズムを変更すると、クライアント側に追加の変更を強いることになります。 - プロバイダーは各コンシューマーを特定し、そのオーナーに連絡できるか? 安定したアプリケーションID、SDKバージョン、およびオーナーの連絡先により、正確な移行追跡が可能になります。匿名トラフィックの場合は、より保守的な廃止ゲートが必要になります。
- 法的、契約上、またはビジネス上のどのサポート期間が約束されていたか? シャットダウン日は、公開されたポリシー、顧客への義務、リスク、および実際の採用状況から導き出されます。他プラットフォームのサポート期間は普遍的なルールではありません。
30秒の回答フレームワーク
「まずv1の文書化された規約と観測可能な動作を凍結し、次に各提案をソース、ワイヤ、セマンティックの各互換性に分類します。デフォルトの動作が変わらない新しいオプショナルフィールドはv1に収まる可能性があります。文字列からオブジェクトへの置換、フィールドのリネーム、および全件取得リストからページネーションへの変更にはv2が必要です。新しいレスポンスenum値は、オープンenumポリシーと古いSDKの動作に依存します。Ordersのドメインロジックを共有し、v1とv2の表現アダプターのみを維持します。リリース前に、OpenAPIの差分チェック、古いSDKテスト、記録されたリクエストの再生、およびE2Eのコントラクトテストを実行し、その後、既知のコンシューマーがv2へオプトインできるようにします。コンシューマーごとに採用率とエラーを監視し、移行、非推奨、およびシャットダウンの日付を公開し、移行と契約義務のゲートを通過した後にのみv1を廃止します。障害が発生した場合はいつでも、v1を変更することなくv2のルートまたはアダプターをロールバックできます」
ステップごとの詳細解説
まず、互換性のベースラインを構築することから始めます。現在のOpenAPIドキュメント、リリース済みSDK、代表的なリクエストとレスポンス、エラーコード、ソート順、デフォルト値、およびリストの動作を保存します。ドキュメント化されていないものの外部から観測可能な動作もサンプリングします。コンシューマーはフィールドのフォーマット、nullの扱い、順序、または1つのレスポンスで完全な結果セットを取得することに依存している可能性があるためです。同時に、コンシューマーID、バージョン、リクエスト量、およびオーナーを記録します。これにより、後で「未使用」と「プロバイダーが特定できない誰かによって使用中」を区別できるようになります。
次に、提案されたすべての変更を分類します。
| 提案された変更 | v1での評価 | 対処方針 |
|---|---|---|
| 省略時に以前の動作が維持されるオプショナルなリクエストフィールドの追加 | 通常は互換性あり | v1に追加し、古いリクエストでテスト |
| オプショナルなレスポンスフィールドの追加 | 条件付きで互換性あり | まず未知フィールドのポリシーと古いSDKを検証 |
customer_nameをcustomerオブジェクトに置換 | 互換性なし | v1では文字列を維持し、v2でオブジェクトを返却 |
| 既存フィールドの削除またはリネーム | 互換性なし | 新しいバージョンで新しい名前を追加し、v1からは削除しない |
| デフォルトで全件取得リストをページネーションに変更 | セマンティック互換性なし | v2でカーソルとページのセマンティクスを定義 |
レスポンスenumへのrefundedの追加 | enumの規約に依存 | オープンenumのルール、生成コード、網羅的switch文を検証 |
| 妥当な依存関係に影響しないドキュメント化されていないスペルミスの修正 | 依然として証拠が必要 | コンシューマーテストとトラフィック再生で実証 |
ページネーションは過小評価されやすい典型例です。Googleの互換性ガイダンスでは、以前はすべての項目を返していたAPIに有限のデフォルトpage_sizeを追加するリスクを指摘しています。古いクライアントが最初のレスポンスで完了したと誤認する可能性があるためです。v2でitems、next_page_token、ソート順、およびトークン無効化ルールを定義します。サポート期間中、v1は元のセマンティクスを維持し、クォータ、レスポンスサイズの監視、および移行のアウトリーチによって運用リスクを制御します。
次に、バージョンの境界を設定します。設問では既にパスバージョニングが使用されているため、/v2/ordersを追加します。同一パス上でUser-Agentからバージョンを推測したり、v1を新しいセマンティクスを持つ表現へ暗黙的にルーティングしたりしないでください。外部表現のみをバージョン管理します。各リクエストを同一のドメインコマンドにパースし、注文クエリと認可ロジックを共有した上で、V1OrderPresenterまたはV2OrderPresenterを使用して対応する構造を生成します。サービスを複製することなく、セキュリティ修正やビジネスルールを両方のバージョンに適用できます。
代表的なv2レスポンスの例は次の通りです。
{
"orders": [
{
"id": "ord_1",
"customer": {
"display_name": "Ada Lovelace"
},
"status": "paid"
}
],
"next_page_token": "eyJvcmRlcl9pZCI6Im9yZF8xIn0"
}4つのリリースゲートを使用します。第1に、新旧のOpenAPI定義を比較し、v1のフィールド削除、必須/任意の変更、型の変更、意図しない新しいバリデーションルールを拒否します。第2に、未知のフィールド、null、エラーレスポンス、enumを含む固定の規約ケースを、最新の公開v1 SDKでコンパイルおよび実行します。第3に、代表的なサニタイズ済みリクエストを再生し、新旧のv1実装間でステータスコード、クリティカルなフィールド、順序を比較します。第4に、既知のコンシューマーの少人数グループにサンドボックスまたはカナリア環境でv2へのオプトインを許可し、機能、4xx、5xx、レイテンシ、レスポンスサイズを観測します。スキーマの差分は構造的な変更を検出できますが、順序、デフォルト値、またはページの完全性に関するセマンティックなアサーションの代わりにはなりません。
移行はオプトインから始まります。v2のドキュメント、SDK、フィールドごとの移行対応表、および両バージョンをサポートするサンドボックスを公開します。既知の各コンシューマーに対し、v1の呼び出し量、失敗しているエンドポイント、および目標期日を提示します。ガイドの不備を早期に表面化させるため、プロバイダー自身のサンプルコードと公式SDKを最初に移行します。コンシューマーごとに採用状況を確認し、集計リクエストとは別に精査します。低頻度の月末照合連携は、多数のヘルスチェックよりも重要である場合があります。バージョンごとのアクティブなユニークコンシューマー数、リクエスト量、4xx、5xx、p95レイテンシ、ページネーションの完了率、古いSDKのパース失敗、および連絡済みだが移行していない高リスクアカウントを追跡します。
移行ポリシーの公開、APIの正式な非推奨化(deprecation)、およびレスポンス停止の3つのライフサイクルイベントを分離します。RFC 9745は、非推奨日のためのDeprecationレスポンスヘッダーと、補足情報のためのdeprecationリンク関係を定義しています。プロバイダーがそのリソースのレスポンス停止を予定している場合にのみSunsetを追加します。非推奨化自体がリソースの動作を変更してはなりません。以下の日付は面接用の例であり、実際の日付は公開されたポリシーに従う必要があります。
Deprecation: @1803859200
Sunset: Wed, 01 Sep 2027 00:00:00 GMT
Link: <https://api.example.com/migrations/orders-v2>; rel="deprecation"; type="text/html"v1を廃止する前に、以下のすべてを満たすことを必須とします。サポート義務が果たされていること、既知の重要なコンシューマーが移行済みであるか承認された例外を受けていること、残存トラフィックの理由が説明されていること、移行ガイドとサポートチャネルが機能していること、v2のエラー・レイテンシ・ビジネス成果のゲートを通過していること、およびシャットダウンのリハーサルがロールバック可能であること。高価値の顧客が依然としてブロックされている場合は、サポートを延長するか、制限付きの互換ゲートウェイを提供するか、契約に従ってください。100%の採用率を見せるためだけにその顧客を無視してはなりません。
リリース前にロールバックを定義します。v2のルートとプレゼンテーションアダプターは個別に無効化可能であり、ドメインの書き込みは後方互換性を維持し、v1は最後に検証されたアーティファクトを保持します。v2のフィールドに新しいストレージが必要な場合は、v2がそれを読み取る前にストレージを拡張およびバックフィルします。v2リリースにおいてv1に必要なデータを削除してはなりません。v2のロールバックとは、その実装を復元することを意味します。インシデントを隠蔽するために「v2」の意味を変更することは、再び規約違反となります。
質の高い模範解答
「規約の変更とリリースのライフサイクルを切り離して考えます。これらのクライアントは強制アップグレードができないため、同一リクエストに対してJSONのパース、古いSDKの実行、および同じ意味を持つ完全な結果を維持する必要があります。
customer_nameを文字列からオブジェクトに変更すると型が変わり、リネームは削除と追加の操作となるため、どちらもv2に属します。GET /ordersをデフォルトでページネーション化すると、古いクライアントで結果の欠落が発生し、セマンティックな破壊となるため、これもv2に属します。refundedの追加を安易に安全だと決めつけることはしません。レスポンスenumがオープンとしてドキュメント化されており、古いSDKが未知の値を保持できるのであれば、v1を拡張できます。生成されたコードがクローズドenumを使用している場合や、コンシューマーが網羅的switch文を使用している場合は、新しい値をv2にとどめるか、まず安全な未知値の処理パスを確立してテストします。
/v1/ordersのレスポンスと全件取得のセマンティクスを維持し、構造化されたcustomerオブジェクト、カーソル、ページルールを備えた/v2/ordersを作成します。両バージョンでクエリ、認可、注文状態のロジックを共有し、リクエストのパースとレスポンス表現のみを異ならせます。マージ前に、OpenAPI定義の比較、最新のv1 SDKによるコントラクトテストの実行、およびサニタイズされたリクエストの再生を行い、ステータスコード、順序、デフォルト値、完全性を確認します。社内SDKと少数の既知の連携システムが最初にv2へオプトインします。リグレッションが発生した場合は、v1を変更せずに継続させたままv2ルートを無効化します。
移行期間中は、アプリケーションIDごとにアクティブなコンシューマーを測定し、総トラフィックを補助シグナルとして使用しながら、バージョンの採用状況、パース失敗、4xx、5xx、レイテンシ、ページネーション完了率、およびクリティカルなアカウントを追跡します。ガイドにはフィールドマッピング、ページネーションループ、enumフォールバック、およびテスト環境を含めます。正式な非推奨化はレスポンスヘッダーと移行リンクを通じて検出可能にし、シャットダウンには個別に宣言された日付を設けます。サポート義務、クリティカルなコンシューマー、残存トラフィック、およびv2のSLOがすべてゲートを通過し、可逆的なリハーサルを経た後にのみv1を廃止します。これにより、バージョン識別子、互換性のある実装、移行の証拠、および廃止が、ひとつの検証可能な計画となります」
よくある間違い
- 「URLに
/v2を入れる」とだけ答える → 誰に影響が出るかも、移行やシャットダウンのゲートも特定できていない → v1のベースラインを設定し、すべての変更をソース、ワイヤ、セマンティック互換性に分類する。 - すべてのレスポンス追加が互換性を持つと思い込む → 厳格なデシリアライザー、クローズドenum、網羅的switch文は依然として失敗する可能性がある → 公開規約と生成されたSDKを検証し、実際の旧バージョンによるテストを実行する。
- v1を自動的にv2へリダイレクトする → 1つのバージョン識別子が2つのセマンティクスを表すことになり、クライアントが選択やロールバックを行えなくなる → 安定したv1の表現を維持し、v2の明示的な選択を要求する。
- v1とv2のためにサービス全体をコピーする → セキュリティ修正やビジネスルールが乖離し、デュアルバージョンの運用コストが増大する → ドメインロジックを共有し、真に異なるパースとプレゼンテーションのみを分離する。
- 告知した日付が来たら直ちにv1を停止する → 匿名のロングテール呼び出し、低頻度の照合バッチ、重要な顧客が依然として依存している可能性がある → コンシューマーごとの採用状況、義務、残存トラフィックを確認し、復旧のリハーサルを行う。
- サーバー側の2xx率のみを監視する → クライアントがレスポンスを受信してもパースに失敗したり、ページを脱落させたり、新しいステータスを誤解釈したりする可能性がある → 古いSDKでの結果、ページネーション完了率、エンドツーエンドの実行結果、サポートへの問い合わせシグナルを追加する。
フォローアップの質問と回答
フォローアップ1:同じ会社が所有する3つの内部コンシューマーにv2は必要ですか?
必ずしも必要ありません。すべての呼び出し元が特定可能で、リリースを調整でき、ロールバックが迅速に行える場合は、expand–migrate–contract(拡張–移行–縮小)を使用します。互換性のあるフィールドまたはエンドポイントを追加し、両方の構造を読み取れるコンシューマーをリリースし、プロデューサー側を切り替え、測定された使用量がゼロになった後に古い規約を削除します。コントラクトテストやバージョン管理されたデプロイの証拠は依然として重要ですが、制御可能なコンシューマーであれば恒久的なパブリックv2は不要です。調整されていないオフラインジョブ、古いクライアント、または外部パートナーが1つでも存在すれば、その前提は無効になります。
フォローアップ2:Webhookイベントはアカウントの現在のAPIバージョンに従うべきですか?
リプレイ時に履歴イベントを「現在」のバージョンで再解釈してはなりません。エンドポイント作成時にイベントAPIバージョンを固定し、イベントとともにそれを記録し、リトライやリプレイ用にも元の構造を保持します。アップグレードは、新バージョンのエンドポイントを作成または切り替え、コンシューマーを検証することによって行います。Stripeの公開ドキュメントでも同様に、Webhookイベントの構造はエンドポイント作成時のAPIバージョンに紐づけられています。新旧両方のイベントを送信することは重複した副作用を引き起こすため、コンシューマーが安定したイベントIDで重複排除を行っている場合の短い移行期間としてのみ安全です。
フォローアップ3:新しいレスポンスenum値の追加は破壊的変更ですか?
公開された規約によります。GitHubはenum値の追加を加法的な変更として挙げていますが、Googleの互換性ガイダンスでは古いコードが新しいレスポンスenum値を適切に処理できないリスクについても警告しています。そのトレードオフを明確に述べてください。規約がオープンセットを定義しており、SDKが未知の値の表現を公開し、コンシューマーがそれを許容しなければならない場合、変更は互換性を持つ可能性があります。型がクローズドである場合や、エコシステム内に網羅的switch文が含まれている場合は、破壊的リスクとして扱います。まずSDKと規約を改善するか、その値を新しいバージョンに含めます。サーバー側のスキーマだけで判断することはできません。
フォローアップ4:制限のないv1リストがタイムアウトしています。移行が間に合わない場合はどうしますか?
まず、既存の規約で許可されている制御(クォータ、キャッシュ、クエリの最適化、バックプレッシャー)を用いてサービスを回復させつつ、高トラフィックのコンシューマーを直接v2へ移行させます。緊急のレスポンス上限設定が避けられない場合は、v1を破壊する可能性があることを明示し、インシデントおよび変更承認プロセスを使用し、影響を受ける範囲を告知し、一括エクスポートや一時的な互換チャネルを提供し、データ欠落リスクを監視します。200レスポンスで最初の100件を暗黙的に返すことは、可用性インシデントを検知困難なデータエラーへと変えてしまうため、互換性のある修正ではありません。
フォローアップ5:シャットダウン日が到来しましたが、トラフィックの0.2%が依然としてv1を使用しています。次に何をすべきですか?
その割合をコンシューマーとビジネス目的に分解します。プローブ、設定ミス、月末処理ジョブ、または契約中の顧客などです。ビジネス上の依存関係がない特定可能なトラフィックは排除します。クリティカルなコンシューマーには、アップグレード、例外措置、またはサポートのエスカレーションが必要です。匿名トラフィックは、公開されたポリシーとリスクモデルに基づいて対処します。残存する呼び出し、通知の証拠、サポート義務、復旧計画、および決定責任者を記録します。割合の数字単体では、シャットダウンが安全であることも、バージョンを永久に残さなければならないことも証明できません。