フロントエンドとAPIの互換性を維持する方法

新しいフロントエンドのビルドができあがりました。チームのレビューも通り、テストもパスし、バンドルはCDNに配置されてデプロイを待っています。しかし、スイッチを切り替える前に、よく見落とされる質問があります。「このフロントエンドは、本番環境で稼働中のAPIと正しく動作するのか?」

フロントエンドとバックエンドが同時にリリースされることはほとんどありません。APIが古いバージョンを提供しているのに、フロントエンドが新しいエンドポイントを期待しているかもしれません。あるいは、APIチームが互換性を壊す変更をデプロイしたのに、フロントエンドはその変更が入る前にビルドされたかもしれません。どちらにせよ、ユーザーは壊れたページ、欠落したデータ、誰かが苦情を言うまで気づかれない暗黙のエラーに直面することになります。

これはツールの問題ではありません。パイプラインに現れる調整(コーディネーション)の問題です。

なぜフロントエンドとAPIの同期がずれるのか

フロントエンドとバックエンドでは、リリースのリズムが根本的に異なります。CDNに置かれた静的フロントエンドは即座にデプロイできます。サーバーの再起動も、コネクションのドレインも、マイグレーションも不要です。ファイルをプッシュすれば、次のユーザーリクエストがそれを取得します。

バックエンドAPIは違います。新しいAPIバージョンをデプロイするには、アプリケーションサーバーの再起動、データベースマイグレーションの実行、インフラストラクチャ設定の更新が必要になることがよくあります。これには時間がかかり、独自のリスクも伴います。

これらのリズムが合わないと、フロントエンドが期待するものとAPIが返すものにズレが生じる期間が発生します。フロントエンドが存在しないエンドポイントを呼び出したり、APIが新しいフィールドを返し始めて古いフロントエンドがレスポンスをパースできずにクラッシュしたりします。

フロントエンドとAPIを別々のチームが担当している場合、問題はさらに悪化します。誰かがバグを報告するまで、フロントエンドチームはAPIが変更されたことすら知らないかもしれません。

APIバージョニングは古典的な解決策だが、コストがかかる

最も一般的な解決策はAPIをバージョニングすることです。URLに /api/v1/orders/api/v2/orders のようにバージョンプレフィックスを付けます。古いフロントエンドはv1を呼び続け、新しいフロントエンドはv2に移行します。古いフロントエンドが完全に廃止されるまで、両方のバージョンが共存します。

これは機能しますが、コストがかかります。複数のAPIバージョンを維持することは、バックエンドチームに技術的負債をもたらします。新しい機能はすべて両方のバージョンに実装するか、非推奨スケジュールを計画する必要があります。ユーザーは最終的に移行する必要があり、その移行自体がプロジェクトになります。

バージョニングはセーフティネットですが、重いセーフティネットです。頻繁にリリースするチームにとって、2つか3つのAPIバージョンを維持することは、開発速度の低下につながります。

フィーチャーフラグでより柔軟に制御する

より柔軟なアプローチは、フィーチャーフラグを使用することです。新しいAPI呼び出しを含む新しいフロントエンドをバンドルに同梱して出荷しますが、それらの呼び出しはオフになっているフラグの背後にゲートされます。ユーザーは新しいフロントエンドをダウンロードしますが、機能が無効なので新しいエンドポイントにはアクセスしません。

APIチームがデプロイを完了したら、ダッシュボードからフラグをオンにします。フロントエンドは新しいダウンロードを必要とせずに新しいエンドポイントの呼び出しを開始します。アプリストアのレビューも、CDNキャッシュのパージも、調整ミーティングも必要ありません。

フィーチャーフラグは、フロントエンドとAPIを別々のチームが担当している場合に特に便利です。フロントエンドチームは自分のスケジュールで出荷でき、APIチームも自分のスケジュールで出荷できます。フラグが単一の調整ポイントになります。

トレードオフは、まだアクティブになっていないコードを出荷することです。そのコードはテストされレビューされていますが、ユーザーのブラウザで何もせずに待機しています。バンドルサイズが気になる場合は、どれだけのデッドコードを出荷するか注意する必要があります。

コントラクトテストでリリース前に問題を発見する

フィーチャーフラグとバージョニングは、問題が発生した後の調整を処理します。しかし、フロントエンドが本番環境に到達する前に非互換性を発見することもできます。その仕組みは、CI/CDパイプラインでのコントラクトテストです。

仕組みはこうです。フロントエンドのビルド中に、パイプラインは一連のコントラクトテストを実行します。これらのテストは、フロントエンドが期待するAPIレスポンスが、実際のAPIからのレスポンスと一致するかをチェックします。APIがフロントエンドの想定しないフィールドを返したり、フィールドが欠落していたり、データ型が変更されていたりすると、コントラクトテストは失敗し、パイプラインは停止します。

実際の最小限のコントラクトテストは次のようになります。

// contract-test.js - CIでフロントエンドデプロイ前に実行
async function checkUserEndpoint() {
  const response = await fetch('https://api.example.com/v1/users/1');
  const data = await response.json();

  // フロントエンドが期待する構造をアサート
  if (typeof data.id !== 'number') {
    throw new Error('Expected id to be a number');
  }
  if (typeof data.name !== 'string') {
    throw new Error('Expected name to be a string');
  }
  if (!Array.isArray(data.roles)) {
    throw new Error('Expected roles to be an array');
  }

  console.log('Contract test passed: /users/:id matches frontend expectations');
}

checkUserEndpoint().catch(err => {
  console.error('Contract test failed:', err.message);
  process.exit(1);
});

このテストはステージングまたは本番のライブAPIに対して実行されます。APIがフィールドの型を変更したり、必須フィールドを削除したりすると、フロントエンドがユーザーに届く前にパイプラインが失敗します。

これらのコントラクトテストは、戦略に応じてステージングまたは本番で現在稼働しているAPIのバージョンに対して実行します。テストはビジネスロジックをチェックしません。構造だけをチェックします。レスポンスにフロントエンドが必要とするフィールドが含まれているか、そしてそれらが正しい型かどうかです。

コントラクトテストは統合テストの代わりにはなりません。統合テストはシステム全体が正しく動作することを検証します。コントラクトテストは、フロントエンドとAPIがクラッシュせずに通信できることだけを検証します。しかし、互換性の問題については、コントラクトテストこそが必要なものです。高速で、すべてのパイプラインで実行でき、最も一般的なフロントエンド・バックエンドのミスマッチを発見できます。

これらのアプローチを組み合わせて実用的なセーフティネットを構築する

単一のテクニックですべてのシナリオをカバーできるわけではありません。APIバージョニングは長期的な共存を扱います。フィーチャーフラグはリリースタイミングのミスマッチを扱います。コントラクトテストは問題がユーザーに届く前に発見します。

以下のフローチャートは、状況に応じたアプローチの選択に役立ちます。

flowchart TD A[フロントエンドとAPIの同期がずれている?] --> B{大きな破壊的変更か?} B -->|はい| C[APIバージョニングを使用] B -->|いいえ| D{バックエンドの準備ができていない?} D -->|はい| E[フィーチャーフラグを使用] D -->|いいえ| F{リリース前にミスマッチを発見する必要がある?} F -->|はい| G[CIにコントラクトテストを追加] F -->|いいえ| H[対応不要] C --> I[非推奨スケジュールを計画] E --> J[フロントエンドコードを非アクティブで出荷、後でフラグをオン] G --> K[構造ミスマッチでパイプラインが失敗] I --> L[フラグとテストを組み合わせて完全なセーフティネットに] J --> L K --> L

実用的なセットアップは次のようになります。

  • 多くのコンシューマに影響を与える大きな破壊的変更にはAPIバージョニングを使用する。
  • フロントエンドとバックエンドチーム間の機能レベルの調整にはフィーチャーフラグを使用する。
  • フロントエンドパイプラインにコントラクトテストを追加し、互換性のないビルドがデプロイされるのを防ぐゲートとして機能させる。

この組み合わせにより、すべてのチームに同じリリーススケジュールを強制することなく、複数の保護レイヤーを提供できます。

パイプラインのためのクイックチェックリスト

互換性チェックを初めて設定する場合の、開始するための短いリストです。

  • フロントエンドが依存するAPIエンドポイントを特定する。
  • 各レスポンスの構造をチェックするコントラクトテストを作成する。
  • フロントエンドのCIパイプラインで、現在のステージングまたは本番APIに対してそれらのテストを実行する。
  • コントラクトテストが失敗した場合にパイプラインが失敗するように設定する。
  • バックエンドで準備ができていない機能については、フロントエンドの呼び出しをフィーチャーフラグでラップする。
  • 古いAPIバージョンの非推奨スケジュールを計画し、すべてのフロントエンドチームに伝達する。

最も重要なこと

フロントエンドとバックエンドの互換性は、技術的な目新しさではありません。ライブAPIに対してフロントエンドコードを出荷するすべてのチームにとっての日常的な現実です。リスクはコードにバグがあることではありません。リスクはコードが正しいにもかかわらず、間違ったバージョンのAPIと通信していることです。

バージョニング、フィーチャーフラグ、コントラクトテストは、それぞれ問題の異なる部分を解決します。これらを組み合わせて使用することで、パイプラインは互換性のないコードをユーザーから遠ざける信頼性の高いゲートになります。