プロンプトとスコープ
ある公開 API で、機能名、優先度、実験用パラメータを含む Features レスポンスフィールドが必要になりました。複数の言語で書かれたクライアント、CDN、ゲートウェイ、SDK がこれを読み取ります。レガシーなクライアントは未知のフィールドを無視することしかできません。フィールドのフォーマット、シリアライズおよびパースのルール、互換性ポリシー、検証計画を設計してください。
これはバックエンドの API コントラクトに関する設問です。重要なのは、「文字列風のヘッダー」を相互運用可能なプロトコルへと昇華させることです。RFC 8941 は共通の Item、List、Dictionary、およびパラメータのモデルを定義しており、RFC 9651 はその改訂版です。RFC 9110 は、新しいフィールドに対して文法を明記し、危険な制御文字を拒否することを求めています。すべての RFC アルゴリズムを実装する必要はありませんが、境界を定義する必要があります。
面接官が見ているポイント
面接官は、単にカンマ区切りのテキストを連結するのではなく、List、Dictionary、Item のいずれかを選択する前にセマンティクスを定義しているかを確認したいと考えています。優れた回答には、送信者のシリアライズ、受信者のパース、未知のメンバー、重複フィールドの結合、サイズ制限、テレメトリが含まれます。
不十分な回答は、単一のサンプル値を示すにとどまります。優れた回答では、機能セットがなぜ Dictionary なのか、なぜパラメータキーが小文字なのか、なぜ任意の非 ASCII テキストを String に隠してはならないのか、そしてパース失敗時に誤って機能を有効化しないようにする方法を説明します。最近のバックエンド API 面接ガイドでも、安定したコントラクト、後方互換性、障害モードが強調されています。
最初に明確にすべき質問
フィールドのセマンティクスと信頼境界
これがヒントなのか、認可の決定なのか、ビジネス上の事実なのかを確認します。権限、課金、セキュリティに影響を与える場合、サーバーがオーソリティ(信頼の主体)であり続け、クライアントからのエコーを信頼してはなりません。フィールドがキャッシュ可能かどうか、またユーザーごとに値が変化するかどうかを尋ねます。
型と互換性のエンベロープ
値が順序のない機能セットなのか、順序付きの優先度リストなのか、単一のバージョン識別子なのかを尋ねます。古いクライアントが動作し続ける必要があるか、新しいパラメータが追加される可能性があるか、ゲートウェイが重複するフィールド行を結合するかどうかを確認します。これらの回答によって、コンテナの型と未知のメンバーに対するポリシーが決まります。
障害とリソースのバジェット
無効な入力によってフィールド全体、単一のメンバー、あるいはレスポンス全体のどれを無視するかを決定します。バイト数、メンバー数、ネストの深さ、パース時間の制限を設定します。これらの選択は DoS 境界の一部となります。
30秒の回答フレームワーク
「これを文字列に隠した JSON ではなく、バージョン管理可能なマシンコントラクトとして定義します。機能セットには Dictionary を使用し、各キーには真偽値またはパラメータ付きの Item を保持させます。キーとパラメータは RFC のシリアライズ規則に従い、未知のメンバーは無視されます。サーバーは制限された ASCII を出力し、合計サイズとメンバー数の制限を強制します。受信者は同じ文法を使用し、無効な構文はフィールドが存在しないものとして扱い、機能が有効であると勝手に推測しません。ゲートウェイでの重複フィールドの結合とキャッシュのバリエーションをテストし、まずはシャドウパースを実施して、ロールアウト前にパース失敗、フィールドサイズ、誤った機能の有効化を比較します。」
ステップごとの解決策
ステップ 1: コンテナを選択する前に値をモデル化する
search;v=2, upload=?1 のような機能スイッチには Dictionary を使用します。各メンバーに意味のある順序やパラメータがある場合は List を使用します。単一のバージョンやポリシー名には Item を使用します。単に利便性のためだけにヘッダー内に JSON を配置してはなりません。中間者や SDK は依然として専用のパーサーを必要とします。
ステップ 2: 拡張可能なフィールドを定義する
search や upload などのキーを定義します。v=2 や tier="pro" などのパラメータには、構造化フィールドの文法で許可されている型のみを使用します。パラメータキーは小文字にします。表示テキストはフィールド外に保持するか、すべての中間者を確認した上で明示的にサポートされている Display String 拡張を使用します。各メンバーのセマンティクス、デフォルト値、無効な値の場合の影響をドキュメント化します。
Features: search;v=2, upload=?1同等の値によって不要なキャッシュキーや署名の差異が生じないよう、シリアライズは決定論的(デターミニスティック)である必要があります。受信者は文法を理解するパーサーを split(',') で置き換えてはなりません。カンマ、丸括弧、引用符、パラメータには定義された境界があります。
ステップ 3: 重複フィールドと未知のメンバーを指定する
まず、重複行が許可されるかどうかを規定します。フィールドが Dictionary の場合、ミドルウェアが行を結合する可能性があるため、コントラクトで結合時の意味と重複キーに対する競合ルールを定義する必要があります。未知のキーやオプションのパラメータはデフォルトで無視します。既知のキーに無効な型が含まれている場合は、そのメンバーまたはフィールド全体を破棄しますが、その選択を規範的な仕様(normative)とします。パースに失敗したからといって、セキュリティスイッチが有効化されるようなことがあってはなりません。
ステップ 4: 厳格かつ実用的なパース境界を構築する
詳細なパースを行う前に、合計バイト数、メンバー数、ネストの深さ、CPU 時間を制限します。CR、LF、NUL、その他フィールドの文法外の文字を拒否します。外部入力に対して定数時間パースは不要ですが、例外パスが巨大な値を繰り返しパースしてはなりません。機密情報を含む可能性のある完全なフィールドをログ出力することなく、カテゴリ分けされた障害を記録します。
ステップ 5: キャッシュ、署名、進化を処理する
フィールドがユーザーや実験コホートによって異なる場合は、適切な Vary レスポンスの挙動またはプライベートキャッシュを使用します。そうしないと、CDN があるユーザーの機能を別のユーザーに公開してしまう可能性があります。フィールドが HTTP Message Signatures の対象である場合、署名者と検証者は同じ正規化された構造化値を必要とします。追加されたキーやオプションのパラメータは、古いクライアントにとって無視可能なままであるべきです。セマンティクスの削除や変更には、バージョンまたは移行期間が必要です。
ステップ 6: ロールアウトと反例で設計を検証する
まずは挙動を変更せずにシャドウパースを行い、次に小規模な内部コホートで有効化します。重複キー、空のリスト、壊れた引用符、未知のパラメータ、サイズ超過フィールド、プロキシによる結合、キャッシュの不一致をテストします。フィールドの有無によるパース成功率、誤った有効化、レスポンスバイト数、CPU、キャッシュヒット率、SDK バージョンを比較します。あらゆるパース失敗は安全なデフォルトへとフォールバックさせます。
高品質な回答例
私はこれをプロトコル設計の問題として扱います。まず、そのフィールドが単なる機能のヒントなのかを確認します。認可を制御する場合、サーバーがオーソリティのままとなります。機能セットに対しては Structured Field Dictionary を選択し、各キーを真偽値またはパラメータ付き Item として定義します。順序のない機能に対して List は使用しません。コントラクトでは、シリアライズ、パラメータの型、重複行、未知のメンバー、および無効な値の影響を規定します。
送信者は制限された ASCII を出力し、フィールドサイズとメンバー数の制限を強制します。受信者はカンマで分割するのではなく RFC 互換のパーサーを使用し、制御文字を拒否し、未知のキーを無視し、型が誤っている既知のキーは存在しないものとして扱います。ゲートウェイには単一の固定された重複結合ルールを持たせ、偶発的な「最後の値が優先」には決して依存しません。
また、キャッシュと署名の挙動も確認します。ユーザー固有の機能には Vary、プライベートキャッシュ、またはキャッシュ不可の設定が必要であり、署名の両側で値を同一に正規化する必要があります。シャドウパースを実施した後、パース失敗、誤った有効化、サイズ、CPU を監視しながらフィールドをカナリアリリースします。古いクライアントはフィールドを無視し続け、そのバージョンを明示的にサポートするクライアントのみが新しい挙動を有効にします。
よくある間違い
- 文字列ヘッダーに JSON を入れる → プロキシや SDK でカスタムパースが必要になり、エスケープや重複のセマンティクスに不整合が生じる → RFC の Item、List、または Dictionary モデルを使用し、メンバーの意味を文書化する。
split(',')でパースする → 引用符、内部リスト、パラメータの区切り文字が誤って切り詰められる → 文法を理解するパーサーと無効な構文のテストを使用する。- すべての未知のパラメータで失敗させる → 新しい送信者が古いクライアントと相互運用できなくなる → 既知の安全制約に違反しない限り、拡張パラメータは無視する。
- パース失敗後に機能を有効化する → 切り捨てや中間者による改変が、意図しない実験の適用や権限の付与につながる可能性がある → 安全なデフォルトに戻し、カテゴリ分けされた障害を記録する。
- キャッシュのバリエーションを無視する → CDN がパーソナライズされた機能を別のユーザーに再利用してしまう →
Varyを設定するか、プライベートキャッシュを使用するか、レスポンスをキャッシュしないようにする。
フォローアップの質問と回答
フォローアップ 1: なぜレスポンス JSON に入れないのですか?
機能がボディ内の単なるビジネスデータである場合は、JSON の方が適している可能性があります。Structured Fields は、HTTP メタデータ、ゲートウェイ、キャッシュポリシーがボディを読み込む前に値を検査する必要がある場合に有用です。両方の表現に独立した権威を持たせてはなりません。両方が存在する場合は、優先順位を定義し、不一致を検出します。
フォローアップ 2: 複数のプロキシがフィールドを結合した場合はどうなりますか?
重複行が有効かどうかを規定し、信頼できる境界でそれらを単一のパーサー入力に正規化します。Dictionary の場合、重複キーを拒否するか明示的な競合ルールを定義します。たまたま最後になった値に依存してはなりません。統合テストでは、HTTP/1.1 の重複行、HTTP/2 のフィールド表現、および実際の CDN パスをカバーする必要があります。
フォローアップ 3: 新しいパラメータに非 ASCII テキストが必要な場合はどうしますか?
選択した文法によって制限されている String 型に UTF-8 を直接配置しないでください。サポートされている Display String 拡張を定義して検証するか、表示テキストをボディに保持してフィールドには安定した識別子を持たせます。すべての中間者と SDK がその型をサポートした後にのみ有効化します。
フォローアップ 4: パーサーが CPU スパイクを引き起こしました。どう対応しますか?
直ちにバイト数、メンバー数、ネストの深さ、パラメータ数の制限を厳しくし、制限を超えたフィールドは存在しないものとして扱います。完全な値ではなく、診断用の入力ハッシュと障害カテゴリを保持します。パース処理を制約付きのワーカーに移行することで影響範囲を縮小できますが、文法制限やカナリアロールバックの代わりにはなりません。