The source code for this blog is available on GitHub.

RESTからGraphQLまでを見渡す:API設計のための基本概念

Cover Image for RESTからGraphQLまでを見渡す:API設計のための基本概念
Saito Mai
Saito Mai

はじめに

近年、マイクロサービスアーキテクチャの普及やクラウドサービスの高度化により、APIはさまざまなサービス同士をつなぐ基盤技術としてますます重要視されています。モバイルアプリやウェブフロントエンドとの連携、他企業や外部パートナーとのデータ共有など、「APIを介して何かと連携する」というシーンは現代のソフトウェア開発では避けて通れません。

一方で、APIをどのように設計・構築するかは一筋縄ではいきません。REST、RPC、GraphQLといった通信モデルの選択や、API仕様の公開手段、バージョン管理手法、セキュリティ対策など、考慮すべき要素が多数存在します。もちろんソフトウェアアーキテクチャに正解はないので、明確なベストプラクティスを提示することは誰にもできませんが、技術的判断の妥当性を増やすための情報を増やすための努力は常に重要です

  1. RESTとRPC(gRPC)の比較
  2. GraphQLの概要
  3. API仕様化(OpenAPIなど)の重要性
  4. バージョン管理(セマンティックバージョン管理を中心に)
  5. 通信・データ転送のパフォーマンスやセキュリティなどの考慮点

この構成で、ざっくりAPI設計のトレードオフについて一般論を論じてみようと思います。


■ RESTの基本思想とその特徴

◇ RESTとは何か

REST(REpresentational State Transfer)は、Roy Fielding氏の博士論文で提示されたアーキテクチャ上の制約モデルを指します。もっとも一般的には、HTTPをトランスポートプロトコルとして利用したサービスを指して「RESTful API」と呼びます。

  • ドメインモデル(リソース)の表現

    RESTでは、提供者(サーバ)と利用者(クライアント)がやり取りする「リソース」をHTTPのURIで表現します。たとえば /Attendees/Attendees/1 のように、カンファレンス出席者リソースやそのIDを含むURIを定義し、それに対して操作(GET, POST, PUT, DELETEなど)を行うことでデータを取得・更新します。

  • ステートレス(無状態)

    各リクエストは、過去のやり取りに依存せず、必要なコンテキスト情報をすべて含む必要があります。セッションなどの状態をサーバが保持しないことにより、可用性の向上やスケーラビリティの確保がしやすくなります。

  • 標準化されたインターフェース(HTTPメソッド, ステータスコードなど)

    HTTPには既に豊富なメソッド(GET, POST, PUT, DELETE など)とステータスコード(200, 404, 500 など)が用意されています。RESTful APIはこの仕組みを活かして統一感を提供しやすいのが特徴です。

  • キャッシュ可能

    Webの仕組みであるHTTPキャッシュ機構を活用することで、重い処理やアクセスの集中を緩和できます。キャッシュコントロールを正しく設定することが重要です。

こうした特徴から、RESTは「複数の外部クライアントが広く利用する公開API」に適しているケースが非常に多いといえます。たとえば、外部パートナーがJSON形式でデータを取得して処理するようなシーンや、モバイルアプリ向けにHTTPベースのAPIを提供するシーンが典型です。

◇ リチャードソン成熟度モデル

RESTを理解する際によく使われるのがリチャードソン成熟度モデルです。これは、APIを段階的に以下のレベルで分類します。

  • レベル0:HTTP/RPC単一のエンドポイントを用意し、そこに動詞相当のパラメータを付与して呼び出す(実質的にRPCをHTTPで包んだだけ)ような形。
  • レベル1:リソースリソースをURIで表現し、IDなどを付与してアクセスする。
  • レベル2:メソッドリソースに対してHTTPメソッド(GET, POST, PUT, DELETE など)を活用し、動詞をHTTP標準に沿って分離する。
  • **レベル3:HATEOAS(ハイパーメディアコントロール)**レスポンスに次のアクションへのリンク等を含め、APIをナビゲート可能にする。

実務では、レベル2までが一般的です。レベル3のHATEOASは、動的なクライアント実装やユーザインターフェースをWebブラウザのように柔軟に制御したい場合に有用ですが、サービス間通信では煩雑になりがちです。


■ RPC(gRPC)の概要と特徴

◇ RPCとは

RPC(Remote Procedure Call)は、その名の通り「リモートにあるメソッドを呼び出す」ための仕組みです。RESTがリソースを中心とした抽象化を行う一方で、RPCは「呼び出したい機能」を直接的に定義するのが特徴です。これにより、「APIの設計=メソッドシグネチャの設計」に近い形になります。

◇ gRPC

gRPCは、Googleが中心となって開発し、現在はCNCF(Cloud Native Computing Foundation)のもとで活発にメンテナンスされているオープンソースの高機能RPCフレームワークです。gRPCの大きな特徴は以下の通りです。

  • Protocol Buffers (protoファイル) によるスキーマ定義.proto という宣言ファイルに「サービス」と「メッセージ(リクエストやレスポンスの構造)」を定義し、そこからサーバ・クライアント双方のコードを自動生成できます。
  • HTTP/2ベースの通信HTTP/2はヘッダの圧縮や多重化などを標準サポートしており、HTTP/1.1に比べて効率的な通信を行えます。gRPCはこれを活用することで高速かつ低オーバーヘッドな呼び出しを実現します。
  • バイナリ形式のデータ転送Protocol BuffersはJSONよりもコンパクトなバイナリエンコードを使います。これはペイロードが大きい通信や、短時間に大量のリクエストを行うケースで有利に働きます。
  • 双方向ストリーミングやユニARY通信など多様な通信パターンRESTのような1リクエスト1レスポンスにとどまらず、ストリーミングで連続データを送受信できます。リアルタイム性が求められるケースや大量データの処理に適しています。

◇ 使いどころ

  • 内部トラフィックが中心のマイクロサービス同じ組織・チームでサーバとクライアントの両方を一元管理でき、高速かつ疎通の安定性が重視される場合に最適です。
  • 高スループット・低レイテンシ要件大規模なデータ転送や高頻度通信が必要なとき、Protocol BuffersとHTTP/2による最適化が効いてきます。

一方、外部向けのAPIとしてgRPCを直接公開すると、ブラウザからのアクセス対応が難しい(追加のWebトランスポート層が必要)などのハードルがあります。そのため、「外部向けにはREST、内部向けにはgRPC」 という設計はよく見られるパターンです。


■ GraphQLの概要

◇ GraphQLとは

GraphQLはFacebook(現Meta)が開発したクエリ言語であり、APIの通信様式を定義するためのフレームワークです。特徴的なのは、クライアント側が「取得したいフィールド」を明示的にリクエストし、サーバは「必要なデータのみを返す」仕組みを備えている点です。

  • オーバーフェッチやアンダーフェッチを防ぐRESTではエンドポイントごとに取得できるデータが固定されがちで、「本当は一部のフィールドだけほしいが、大きなレスポンスをすべて取ってくる(オーバーフェッチ)」状況や、逆に「ほしいデータがまとめて取得できず、何回もリクエストしなければならない(アンダーフェッチ)」といった問題が起こりがちです。GraphQLでは、クライアントが必要なフィールドを指定できるため、これらを軽減できます。
  • 単一のエンドポイントで複数リソースを横断的に取得可能RESTでは複数のエンドポイントを呼び分ける必要があるケースでも、GraphQLなら一度のクエリで関連データをまとめて取得できます。
  • スキーマ言語による型定義GraphQLスキーマ言語を用いて、オブジェクトタイプやクエリ・ミューテーションの構造を定義します。これをもとに型安全なコード生成や強力なドキュメント生成が可能です。

◇ 活用シーン

  • クライアントに大きな自由度が必要なケースたとえばフロントエンドが多彩な画面レイアウトを持ち、必要なデータが画面ごとに大きく変わる場合に有効です。
  • 複数サービスやデータソースをまとめて参照させたい場合GraphQLサーバがバックエンドの「集約レイヤ」(ファサード)として機能することで、クライアント側は複数APIへのコールを意識しなくて済みます。

ただし、GraphQLは複雑なスキーマの管理やパフォーマンス最適化が難しい場合があります。また、クエリの柔軟性ゆえに、サーバ側でN+1問題(サブオブジェクトを取得するために追加クエリが大量発生する問題)への対処が必要になったりもします。


■ API仕様化とOpenAPI

◇ OpenAPIとは

OpenAPI(旧名:Swagger)は、REST APIの仕様をYAMLまたはJSON形式で定義・共有するための標準仕様です。APIのエンドポイント、HTTPメソッド、リクエストやレスポンスのパラメータ・ボディなどを機械可読な形式で記述できます。

  • ドキュメント生成OpenAPIファイルから自動生成されるドキュメントは、API利用者にとって分かりやすいリファレンスとなり、また多くのツールが「Swagger UI」などを使ったインタラクティブなドキュメントを提供します。
  • クライアント・サーバコード生成OpenAPI Specificationを使えば、Java, TypeScript, Python, Goなど多くの言語でクライアントSDKやサーバスタブを自動生成できます。
  • テスト・バリデーションOASを用いて、リクエストやレスポンスが仕様に沿っているかの検証を自動化することも可能です。外部との連携でセキュリティ境界(DMZ)を置く場合などに、仕様に沿わないメッセージを遮断する仕組みを取り入れることができます。

◇ バージョン管理との組み合わせ

OpenAPI仕様を用いてAPIを提供している場合、変更履歴の差分ツール(例:openapi-diff)などを活用すると、後方互換性を壊す変更(破壊的変更)が行われたかどうかを自動検知できます。

  • セマンティックバージョニングMajor.Minor.Patch のような形式でAPI全体のバージョンを付与し、破壊的変更(Majorアップ)なのか、後方互換性のある変更(Minor or Patchアップ)なのかを明確化すると、利用者側がアップグレード戦略を立てやすくなります。

■ APIのバージョン管理

◇ バージョニングの重要性

APIが進化するにつれ、新しい機能の追加や不要になったフィールドの削除などが発生します。しかし、利用者のアプリケーションは常に最新へ追随できるとは限りません。そこで、以下のようなアプローチを組み合わせることで、利用者に影響を少なくしつつAPIを進化させられます。

  1. 古いバージョンを残して新バージョンを追加/v1/Attendees を残しつつ /v2/Attendees を追加する、など。
  2. 後方互換性を保ったままエンドポイントを更新フィールドを追加するなどであれば既存利用者を壊さないようにできるため、同じURIのままマイナーバージョンアップする。
  3. **後方互換性を無視した刷新(メジャーバージョンアップ)**設計の根本的な変更多発で管理コストがかさむ場合はやむを得ず大規模改修を行い、すべての利用者にアップデートを強制することもある。

◇ セマンティックバージョン管理

  • MAJOR.MINOR.PATCH
    • MAJOR 破壊的変更(非互換)
    • MINOR 後方互換の新機能追加
    • PATCH バグ修正や微小変更
  • 互換性チェックツールOpenAPIに対して openapi-diff のようなツールを使うと、破壊的変更の有無を機械的に検出できるため、リリース管理や自動テストに組み込みやすくなります。

■ REST/RPC/GraphQLの使い分け

◇ 外部向けはREST、内部向けはgRPC?

よくあるパターンとして、以下のような方針が挙げられます。

  • 外部向けAPI(社外パートナー、モバイルアプリ開発者など)はREST参入障壁が低く、HTTPさえ理解すれば試せるため、エコシステムを広げやすい。
  • 社内の複数マイクロサービス間や高トラフィックが想定される通信はgRPC帯域幅やレイテンシを最適化しやすく、サービス間を高効率に連携できる。

一方で、GraphQLは以下のシーンで効果的です。

  • 多彩な画面や複数デバイスで「取りたいデータが都度細かく違う」場合
  • バックエンドに多数のサービスやデータソースがあり、それらを単一の問い合わせで集約して返したい場合

◇ すべてを提供する「マルチフォーマットAPI」はアリか?

理論的にはRESTとgRPC、さらにGraphQLスキーマをすべて同一サービスで公開することも可能ですが、その分だけ下記の課題が増えます。

  • 仕様変化の重複管理REST用のOpenAPIとgRPC用の.protoファイル、GraphQL用のスキーマ定義がそれぞれで進化し、後方互換性や整合性を保つのが難しくなる。
  • バージョニングの煩雑化REST APIが後方互換を保ったが、gRPCは破壊的変更になった…など、それぞれ独自のバージョン管理が必要になる。
  • コスト増複数のAPIを常時メンテナンスする負担が増大。

組織として「外部利用者向けにはRESTを公開し、内部ではgRPCを使い、フロントエンドにはGraphQLを導入している」等の明確な責任範囲と運用ポリシーが定まっていれば、複数フォーマット共存もありえます。しかし、最初から「全方式を一度に導入しよう」とするのは、運用負荷が高いので慎重な判断が必要です。


■ パフォーマンスと通信コストの考慮

◇ データ転送量・帯域幅

JSONは可読性が高い一方で、バイナリ形式よりもデータサイズが大きくなる傾向があります。大量のデータや高頻度のやり取りがある場合、gRPCや独自圧縮を活用するほうが有利です。

◇ HTTP/2やHTTP/3の活用

  • HTTP/2ヘッダ圧縮や多重化により、多数リクエストを効率的に送受信できます。gRPCはデフォルトでHTTP/2を利用します。
  • HTTP/3QUICプロトコル(UDPベース)を用い、さらに改善された遅延制御や多重化を可能にします。今後の動向に注目するとよいでしょう。

◇ エラー処理・セキュリティ

  • ステータスコードやエラーフォーマットの統一RESTではHTTPステータスコードを活用し、エラーレスポンスの構造を標準化(例:{"error": {"code": "400", "message": "invalid request"}})すると利用者が扱いやすくなります。
  • RPCの場合ステータスコードやエラーオブジェクトに相当する部分をライブラリにより取り決め、クライアントSDKが例外やエラーを扱いやすい形にできます。

外部向けAPIであれば特に、スタックトレースや機密情報をエラー本文に含まないよう注意が必要です。


ここまでREST、RPC(gRPC)、GraphQLなどの特徴を見てみると、いずれもAPIを「どう扱うか」にフォーカスしながら、さまざまなユースケースに応じた選択肢を提示していることがわかります。API設計には正解・不正解の境界が明確にあるわけではなく、組織やチームの状況、求められるパフォーマンス、外部利用者の存在の有無などを総合的に勘案して、最適だと思えるアプローチを選ぶことが大切です。

たとえば、外部向けの公開APIであればRESTの採用が無難で、利用者が多く参入しやすい一方、社内のマイクロサービス同士の通信ではgRPCがパフォーマンスに優れ、実装もコード生成を通じて堅牢になる可能性があります。フロントエンドから複雑なデータ取得パターンが要求されるのであれば、GraphQLが有力な選択肢となるでしょう。

ただし、一つのサービスでREST・gRPC・GraphQLのすべてを提供する場合、管理やバージョン管理が煩雑になる可能性があります。複数のアーキテクチャを扱うと、その分だけチームの技術的知識や運用負荷も増大するため、あらかじめ「外部はこれ」「内部はこれ」といった役割分担をはっきりさせておくほうが賢明です。

また、APIのライフサイクルにおいては、バージョン管理(とくにセマンティックバージョニング)が重要です。新しい機能追加であれば後方互換性を保ったマイナーバージョンアップですむかもしれませんが、大幅な設計変更が必要な場合はメジャーバージョンを切り替えて既存利用者に告知するなど、リリース・運用ポリシーを整備しておくと、トラブルを未然に防ぎやすくなります。

最後に、パフォーマンスやセキュリティの観点で言えば、データ転送量やレスポンス速度を気にするならバイナリ形式を採用できるgRPCが強く、外部公開APIではステータスコードやエラーフォーマットを統一したRESTが扱いやすいなど、ケースバイケースでメリット・デメリットがはっきり分かれます。GraphQLはクライアント側に柔軟性をもたせる代わりに、サーバ側の実装がやや複雑になることも念頭に置く必要があります。

要するに、

  • 自社(組織)の技術スタックやノウハウ
  • 利用者が誰か・どの程度の自由度が必要か
  • パフォーマンス要件や取り扱うデータ量
  • バージョン管理・保守コストをどこまで許容できるか

などを見極め、必要に応じてREST、gRPC、GraphQL、いずれか(または組み合わせ)を選ぶのが現実的だということです。最終的には「トレードオフをどこに置くか」をチームやサービスの事情とすり合わせて判断するしかありません。本記事で挙げた考え方やポイントが、少しでも設計や実装の指針の参考になれば幸いです。