API ゲートウェイは、どのアーキテクチャ図にも登場するのに、説明されることはほとんどない箱です。境界に置かれ、時間とともに責務を集めていきます。ルーティング、認証、レート制限、リクエストの書き換え、そして本来持つべきではなかった業務ルールまで。図がゲートウェイのアイコンを通る矢印を 1 本示すだけだと、そのうちどれを担っているのか誰にも分からず、疑問はレビューではなくインシデントの最中に出てきます。
この記事では、よくある構成として、Web アプリ、モバイルアプリ、パートナーが 1 つのゲートウェイの背後にあり、フロントエンドごとにバックエンドを持つ API ゲートウェイのアーキテクチャ図を作ります。ゲートウェイがやるべきこととやるべきでないこと、トークンの検証とレート制限の仕組み、図が実際の疑問に答えられるようにするためにラベルに書くべきことを説明します。
ゲートウェイに置くべきもの
| 責務 | ゲートウェイで行うか | 理由 |
|---|---|---|
| TLS の終端 | はい | 証明書を 1 か所で管理できる |
| パスやホストによるルーティング | はい | クライアントからは入口が 1 つに見える |
| トークンの検証 | はい | 無効なリクエストをサービスに届く前に拒否できる |
| スコープなどによる大まかな認可 | 多くの場合 | orders:read のないパートナーのトークンは Orders に届かない |
| きめ細かな認可 | いいえ | このユーザーがこの注文をキャンセルできるかは、サービスにしか分からない |
| レート制限とクォータ | はい | サービスを守り、パートナーのプランを適用する |
| リクエスト ID、アクセスログ、トレース | はい | 境界の様子を一貫して把握できる |
| 画面のための呼び出しの集約 | BFF で | UI 固有のロジックを共有ゲートウェイの外に置ける |
| 業務ルール | いいえ | テストできるサービス側に置くべきもの |
ロードバランサーはリクエストをインスタンスに振り分けますが、呼び出し元が誰かは知りません。サービスメッシュはシステム内部のサービス間のトラフィックを扱います。ゲートウェイは外部から入ってくるトラフィックを扱い、クライアントのトークン、ルート、クォータを理解しています。多くのシステムでは 3 つすべてを使っています。
境界でのトークンの検証
この例では、クライアントは ID プロバイダーでサインインし、リクエストのたびに JSON Web Token(RFC 7519)を送ります。ゲートウェイは、プロバイダーの公開鍵を JWKS ドキュメントとして取得してキャッシュし、その鍵で署名を検証したうえで、クレームを確認します。発行者(iss)、オーディエンス(aud)、有効期限(exp)です。
ここでは RFC 8725 にある JWT の現在のベストプラクティスに従う価値があります。ライブラリは許可するアルゴリズムを指定できるようにし、それ以外を使ってはいけません。トークンのヘッダーを信用するのではなく、許可リストを設定しましょう。1 つの発行者が複数のアプリケーション向けにトークンを作る場合、トークンにはオーディエンスを含める必要があり、各アプリケーションはそのオーディエンスが自分のものであることを確認しなければなりません。
有効なトークンが示すのは、呼び出し元が誰かです。何をしてよいかは示しません。ゲートウェイは足りないスコープを拒否できますが、このユーザーがこの注文を変更できるかどうかを決めるのは、やはりサービスです。
レート制限
IP アドレスではなくクライアントごとに数える。 NAT の背後では多くのユーザーが 1 つのアドレスを共有することがあり、1 つのスクレイパーが多くのアドレスを使うこともあります。制限のキーには、API キー、OAuth クライアント、またはユーザーを使いましょう。
カウンターを共有する。 ゲートウェイのインスタンスが複数あってそれぞれが独自に数えると、毎分 100 回の制限がインスタンスごとに 100 回になってしまいます。Redis のような共有ストアなら、すべてのインスタンスにまたがってクライアントごとに 1 つのカウントを保てます。
アルゴリズムを選び、書き残す。 固定ウィンドウは 1 分ごとにリセットされ、境界でのバーストを許してしまいます。スライディングウィンドウはそれをならします。トークンバケットは平均レートを保ちながら、決められたサイズまでの短いバーストを許します。
429 で応答する。 RFC 6585 は 429 Too Many Requests を定義しています。レスポンスでは状況を説明し、Retry-After を含めることもできます。RFC 9110 はこれを、HTTP の日付か待機する秒数として定義しています。RFC 6585 は、サーバーがクライアントをどう識別し、リクエストをどう数えるかを定めていません。そこは自分たちの設計なので、図に書きましょう。IETF のドラフトが標準の RateLimit と RateLimit-Policy ヘッダーフィールドを定義していますが、執筆時点(2026 年 10 月)ではまだドラフトです。
カウンターのストアが落ちたときの動作を決める。 フェイルオープンにすれば制限なしで API を動かし続けられ、フェイルクローズにすればサービスを守ってトラフィックを拒否します。どちらも正解になりえますが、偶然ではなく意図して決めたものであることが大切です。
BFF(Backends for Frontends)
Sam Newman の Backends For Frontends パターンは、汎用の API バックエンド 1 つを、ユーザー体験ごとのサーバー側コンポーネントに置き換えるものです。各 BFF は 1 つの UI に絞り込まれ、たいていはその UI を作るチームが保守するので、チームは画面の変化に合わせて API を変えられます。共有の汎用バックエンドはボトルネックになりがちで、記事では、集約を行うエッジのサービスはコードが肥大化しやすいと指摘しています。
BFF はいくつ必要でしょうか。Newman は、Stewart Gleadow による簡単なルールを紹介しています。1 つの体験に 1 つの BFF です。2 つのアプリが同じ体験を提供し、1 つのチームが両方を担当しているなら、BFF を共有できます。たとえば SoundCloud は、iOS と Android のリスナー向けアプリで 1 つの BFF を使っています。
パートナーは事情が異なります。必要なのは画面に合わせた API ではなく、安定したバージョン付きの API です。そのため、パートナーのルートは BFF を通らず、サービスの公開 API へ向かいます。
例:オンラインショップ
このショップには、React の Web アプリ、iOS と Android のアプリ、API で注文を作成するパートナーがあります。WAF がゲートウェイの手前でトラフィックをフィルタリングします。ゲートウェイはトークンを検証し、クライアントごとのリクエスト数を Redis で数え、パスのプレフィックスでルーティングします。Web チームとモバイルチームはそれぞれ BFF を担当しており、どちらの BFF もプライベートネットワーク内の Orders、Catalog、Accounts サービスを呼び出します。パートナーからの呼び出しは、Orders サービスのバージョン付き API へ直接向かいます。
ダイアグラムの内容
- Web app
- Mobile app
- Partner system
- WAF
- API gateway
- Identity provider
- Rate limit counters
- Private network
- Web BFF
- Mobile BFF
- Orders service
- Catalog service
- Accounts service
- HTTPS
- filtered
- sign in (OIDC)
- JWKS, cached
- count per client
- /web/*
- /mobile/*
- /partner/v1/*
図の読み方は次のとおりです。
- 左から右へ、1 つのリクエスト。 クライアントは WAF を経由してゲートウェイを呼び出し、ゲートウェイはそれを BFF かパートナー API へルーティングします。
- 破線のエッジはリクエストの経路の外です。 Web アプリは ID プロバイダーでサインインし、ゲートウェイはプロバイダーの署名鍵を取得してキャッシュします。
- ゲートウェイの下では、 すべてのゲートウェイインスタンスが共有するレート制限のカウンターを Redis が保持しています。
- プライベートネットワークの中では、 各 BFF が自分の画面に必要なサービスだけを呼び出します。
- Markdown ブロック はゲートウェイのポリシーです。何を確認するか、制限値、サービスに任せることを書いています。
ラベルに書くこと
- ルート。 バックエンドごとに、パスのプレフィックスかホストで書きます。
- すべての入口の認証。 トークンの種類、発行者、ゲートウェイが確認する内容です。
- 制限。 キー、回数とウィンドウ、クライアントに返すものです。
- ゲートウェイでのタイムアウトとリトライ。 境界でのリトライは、苦しんでいるサービスへの負荷を何倍にもします。
- 障害時の動作。 ID プロバイダー、カウンターのストア、バックエンドが使えないときにどうなるかです。
- 担当。 ゲートウェイの設定はプラットフォームチームが持つことが多く、BFF は UI チームが持ちます。
よくある間違い
- ゲートウェイの中の業務ロジック。 書き換えや条件分岐は、誰もテストできないルールに育っていきます。ゲートウェイは薄く保ちましょう。
- ゲートウェイを唯一のセキュリティチェックにする。 背後のサービスは、届いたというだけでリクエストを信用してはいけません。ゼロトラストアーキテクチャの図では、すべてのホップでのチェックの描き方を紹介しています。
- IP 単位だけの制限。 共有アドレスの背後にいるユーザーを罰する一方で、アドレスを切り替えるクライアントは見逃します。
- インスタンスごとのローカルカウンター。 制限値がインスタンス数倍になってしまいます。
- すべてを 1 つの BFF で賄う。 それでは再び汎用バックエンドに戻ってしまいます。
- 詳細のない 429。 どれだけ待てばよいか分からないクライアントは推測し、たいていは短すぎる時間を選びます。
ゲートウェイの背後については、マイクロサービスアーキテクチャの図を参照してください。Kubernetes では、Kubernetes Ingress の図が、ゲートウェイの手前に置かれたり、その一部を置き換えたりすることの多いレイヤーを扱っています。
Archboard で作る
- Google または GitHub でサインインし、図を作成します。
- C4 モデルパックから、Web アプリ用とモバイルアプリ用に コンテナ を 1 つずつ、パートナー用に 外部システム を追加します。
- セキュリティパックから WAF と IDプロバイダー を追加します。
- クラウドパックから APIゲートウェイ を、データパックからレート制限のカウンター用に Redis ノードを追加します。
- 基本パックの フレーム を追加して Private network という名前を付け、その中に 2 つの BFF と 3 つのサービス用に C4 モデルパックの コンテナ ノードを置きます。
- 左から右へつなぎ、各ルートにパスのラベルを付け、サインインと鍵の取得は破線にします。ゲートウェイのポリシーを書いた基本パックの Markdown ブロックを追加します。
この例で使うダイアグラムノードは 14 個で、Free プランの範囲に収まります。
よくある質問
API ゲートウェイはロードバランサーと同じものですか? いいえ。ロードバランサーはリクエストをインスタンスに振り分けます。ゲートウェイは API リクエスト、つまりルート、トークン、クォータを理解します。多くのゲートウェイはロードバランサーの背後で動いています。
API ゲートウェイがあれば、サービスメッシュは不要ですか? 両者は別の問題を解決します。ゲートウェイは外部からのトラフィックを扱い、メッシュは相互 TLS やリトライなど、サービス間の呼び出しを扱います。両方を使っているなら、別々に描きましょう。
ゲートウェイが検証済みでも、サービスはトークンを検証すべきですか? はい、自分が依存するクレームについては検証すべきです。ゲートウェイは不正なトークンを早い段階で拒否し、各サービスはそれでも操作の認可を自分で行います。
BFF はいくつ持つべきですか? ユーザー体験ごとに 1 つです。同じ体験を提供し、担当チームも同じ 2 つのアプリなら、1 つを共有できます。
429 レスポンスには何を含めるべきですか?
短い説明と、待機する秒数を示す Retry-After ヘッダーです。そうすれば、クライアントはすぐに再試行せず、待ってから再試行します。