🥇

【REST API / gRPC / tRPC】徹底比較!どれを選ぶべきか?

2024/08/03に公開
2

はじめに

API設計のアーキテクチャスタイルには、REST API、GraphQL、gRPC、tRPCなど、複数の選択肢があります。
私はこれまでに、実務や個人開発でREST API、gRPC、tRPCを使用してきました。その上で今回は、それぞれの特徴と開発の流れについて紹介します。さらに、それぞれのメリット・デメリットを挙げ、特定のケースに適した選択について考察してみました。

API設計のアーキテクチャスタイルの選択に悩んでいる方に、少しでも参考になれば幸いです。

REST API

RESTとは

まずRESTとはシンプルなWEB全体のアーキテクチャスタイルのことです。RESTには4つの原則があり(6つとも言われている)それらを満たすもののことをRESTfulと言います。
※それぞれの原則の詳細は省略します。

  • 統一インターフェース
  • アドレス可能性
  • 接続性
  • ステートレス性

REST APIはRESTの原則に従って設計されたAPIのことです。
HTTPプロトコルを基盤にしており、リソースを一意の識別子(URL)で指定し、HTTPメソッド(GET、POST、PUT、DELETE)を使用して操作します。
データのやり取りはJSONやXML、プレーンテキストなどさまざまなフォーマットで行います。

REST APIの特徴

  • リソース指向: リソース(データオブジェクト)をURLによって識別します。
  • HTTPメソッドの利用:HTTPメソッド(GET、POST、PUT、DELETEなど)を使用して、リソースの操作を行います。これにより、CRUD操作(データの取得、作成、更新、削除といった操作)を直感的に行うことができます。
  • 多言語サポート:HTTPを利用するため、ほぼすべてのプログラミング言語で実装が可能です。Python、JavaScript、Java、C#, PHP、Ruby、Goなど、あらゆる言語でRESTfulなサービスを構築でき、言語に依存しない柔軟なインタフェースを提供します。
  • シンプルで広く採用:シンプルな設計で、既にWeb開発に携わっているエンジニアにとっては直感的であり、多くのwebサービスやアプリケーションで幅広く採用されています。

REST APIの開発の流れ

  1. APIのスキーマを設計する
  2. バックエンドでAPIのロジックを実装する
  3. フロントエンドでAPIを呼び出す実装をする

記事投稿に関するAPIをREST APIで示すと、以下のようになります。

  1. APIのスキーマを設計する
    エンドポイントを設計し、HTTPメソッドやリクエスト・レスポンスフォーマットを定義します。
CRUD操作 URL HTTPメソッド
全記事を取得 /posts GET
特定の記事を取得 /posts/1 GET
記事の作成 /posts POST
記事の更新 /posts/1 PUT
記事の削除 /posts/1 DELETE

スキーマ定義にはOpenAPIなどのツールを使用するとフォーマットが統一されて管理しやすくなります。

OpenAPI

OpenAPIとはREST API の API 記述形式です。
Swagger Editorにコピペするとドキュメントが出来上がります。

openapi: 3.0.0
info:
  title: Blog API
  version: 1.0.0
paths:
  /posts/{id}:
    get:
      summary: "Get a specific post"
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
          description: "ID of the post to retrieve"
      responses:
        '200':
          description: "A single post"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Post'
        '404':
          description: "Post not found"
components:
  schemas:
    Post:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        content:
          type: string
      required:
        - id
        - title
        - content

  1. バックエンドでAPIのロジックを実装する
    例えばNode.jsとexpressを用いた場合のイメージです。
// GET /posts/:id - 特定の記事を取得する
app.get('/posts/:id', (req, res) => {
  const postId = req.params.id;
  // idを元に特定の記事を取得
  const post = posts.find(p => p.id === postId);
  if (post) {
    res.status(200).json(post);
  } else {
    res.status(404).json({ message: 'Post not found' });
  }
});

/posts/:idというURLに対してGETリクエストが来た場合に呼び出されます。
パスパラメーターのidをキーに一意の記事を検索してレスポンスを返します。

  1. フロントエンドでAPIを呼び出す実装をする
    例えばNext.jsとaxiosを用いた場合のイメージです。
useEffect(() => {
    if (id) {
      fetchPostById(id);
    }
  }, [id]);

const fetchPostById = async (postId) => {
    try {
      const response = await axios.get(`/posts/${postId}`);
      setPost(response.data);
    } catch (err) {
      console.log(err)
  };

/posts/:idというURLに対してGETリクエストをし、レスポンスとして一意の記事が返されます。

gRPC

RPCとは

RPC(Remote Procedure Call)は、「遠隔手続き呼び出し」と訳され、手続き(プロシージャ)を遠隔で呼び出すための技術です。これは一般的な関数を呼び出すようなイメージで、別のサーバーやクライアントから各サーバーが提供する関数を呼び出す仕組みです。

RPCは通信方法を示す抽象的な概念であり、通信規約(RPCプロトコル)や、やり取りするデータ形式が異なるさまざまな種類のRPCがあります。以下は代表的なRPCの種類です。

  • gRPC
  • tRPC
  • JSON-RPC
  • XML-RPC

gRPCは、Googleによって開発されたRPCフレームワークです。gRPCは通信プロトコルとしてHTTP/2を使用し、Protocol Buffersという形式で効率よくデータをやり取りします。

Protocol Buffersとは

Protocol BuffersはGoogleが開発したインタフェース記述言語(IDL)です。
データをバイナリ形式に変換することで、テキスト形式に比べて格段にコンパクトかつ高速なデータ転送を実現します。
Protocol Buffersのコンパイラであるprotocは、.protoファイルに定義されたAPIスキーマをもとに、さまざまなプログラミング言語に対応したクライアントやサーバーのコードを自動生成します。

gRPCの特徴

  • 多言語サポート:gRPCは、多くのプログラミング言語をサポートしており、C++, Go, Python, Ruby, JavaScript, Java, C#, Objective-C, PHPなど、様々な言語でクライアントやサーバーを実装できます。
  • 型安全性.protoファイルのスキーマ定義を基に自動生成されたコードによって各言語における型安全性を保証します。
  • 高速で効率的な通信:HTTP/2プロトコルを使用して通信を行っており、ヘッダー圧縮やストリーミング、双方向通信を活用することで、より効率的で高速なデータ転送を実現します。
  • 双方向ストリーミング:クライアントとサーバーの間で双方向のストリーミング通信をサポートしています。これにより、リアルタイムでのデータ送受信や効率的な通信が可能です。

gRPCの開発の流れ

  1. .protoファイルにAPIのスキーマを定義をする
  2. protocコマンドを使って、.protoファイルからバックエンド言語用のコードを生成
  3. バックエンドでAPIのロジックを実装
  4. .protoファイルからフロントエンド言語用のコードを生成
  5. フロントエンドでAPIを呼び出す実装をする

記事投稿に関するAPIをgRPCで示すと、以下のようになります。

  • 記事の取得
    メソッド名:GetPost
    リクエストデータ形式:バイナリ形式(Protocol Buffers)
  1. .protoファイルにAPIのスキーマを定義をする
service BlogService {
  rpc GetPost(GetPostRequest) returns (GetPostResponse);
}

message GetPostRequest {
  int32 id = 1;
}

message GetPostResponse {
  int32 id = 1;
  string title = 2;
  string content = 3;
}

GetPostメソッドのリクエストの型とレスポンスの型をあらかじめ定義しておきます。

  1. protocコマンドを使って、.protoファイルからバックエンド言語用のコードを生成
    .protoファイルのスキーマ定義をもとに例としてGo言語用のコードを自動生成します。
    自動生成されたコードの中には定義したクラスやインターフェース、定義したメソッドを呼び出すためのコードが含まれます。
protoc --go_out=. --go-grpc_out=. blog.proto
  1. バックエンドでAPIのロジックを実装
    生成したコードを使ってGoでAPIのロジックを実装したイメージです。
// GetPostメソッドの実装
func (s *server) GetPost(ctx context.Context, req *pb.GetPostRequest) (*pb.GetPostResponse, error) {
	post := &pb.GetPostResponse{
		Id:      req.Id,
		Title:   "Sample Title",
		Content: "Sample Content",
	}
	return post, nil
}
  1. .protoファイルからフロントエンド言語用のコードを生成
    ブラウザから直接使用することができずフロントエンドで使用する場合はgrpc-webなどのライブラリを別途インストールする必要があります。
    protocコマンドを実行し、.protoファイルからTypescript言語用のコードを生成するイメージです。
protoc \
  --js_out=import_style=commonjs,binary:./generated \
  --grpc-web_out=import_style=typescript,mode=grpcwebtext:./generated \
  blog.proto
  1. フロントエンドでAPIを呼び出す実装をする
    grpc-webクライアントを使用してAPIを呼び出す部分のイメージです。
client.GetPost({ id: 1 }, (error: any, response: any) => {
      if (error) {
        console.error('Error fetching post:', error);
      } else {
        setPost(response);
      }
    });

tRPC

tRPCは、TypeScript向けのRPCフレームワークで、TypeScriptの型システムを活用してクライアントとサーバー間の通信を簡単かつ型安全に行うことができます。

tRPCの特徴

  • エンドツーエンドの型安全性:tRPCは、クライアントとサーバー間でTypeScriptの型を共有することで、フロントエンドとバックエンドで型安全な開発が可能です。
  • コード自動生成が不要:バックエンドで直接APIスキーマを定義するため、コードの自動生成が必要ありません。そのため、すぐに使用でき、開発の手間を大幅に軽減できます。
  • エディタの補完機能を最大限に活用:TypeScriptの型情報を基に、エディタでの補完機能やコードジャンプ機能がフルに活用できます。これにより、開発中のコーディング効率が大幅に向上します。
  • コードの一貫性と可読性:サーバーとクライアントが同じコードベースで型を共有するため、一貫性のある開発が可能です。特に、大規模なプロジェクトでのメンテナンス性が向上します。

tRPCの開発の流れ

  1. バックエンドでAPIのスキーマを定義しロジックを実装する
  2. フロントエンドでAPIを呼び出す実装をする

記事投稿に関するAPIをtRPCで示すと、以下のようになります。

  • 記事の取得
    メソッド名:GetPost
    リクエストデータ形式:JSON
  1. バックエンドでAPIのスキーマを定義しロジックを実装する
    Zodというスキーマ定義ライブラリを用いたイメージです。
  getPost: t.procedure
    .input(z.object({ id: z.number() })) // 直接スキーマを定義
    .query(async ({ input }) => {
      const { id } = input;
      const post = await db.getPostById(id);
      return {
        id: post.id,
        title: post.title,
        content: post.content,
      }; 
    }),
  1. フロントエンドでAPIを呼び出す実装をする
    const post = await client.blog.getPost.query({ id: postId });

バックエンドで作成したAPIを関数を呼ぶように使用することができます。

REST API vs gRPC vs tRPC

REST API、gRPC、tRPCはそれぞれ異なる特徴を持つAPI設計方法です。どれを採用するかは、プロジェクトの要件や技術スタック、チームのスキルセットによって異なりますが、それぞれの開発を体験した上で個人的に感じたメリット・デメリットや、採用の決め手について考えてみました。

REST APIのメリット・デメリット

メリット

  • シンプルで学習コストが低い:ツールやライブラリが豊富なのがありがたいです。リソース指向で、Web設計に基づいているため、ブログなど、一意のリソース(データオブジェクト)を作成・更新・削除する場合に適しています。リアルタイム性のないアプリケーションには基本的にREST APIで十分だと感じました。

デメリット

  • 型管理をするのが面倒:APIの型付けのためにtype.tsファイルなどを用意しないといけないのがかなりネックだと感じます。(おそらく型付けを自動生成してくれるいい方法あります)
  • APIのバージョン管理が煩雑:実装と設計書の乖離が発生しやすいです。いつの間にかAPI仕様が変更され、フロントエンドが最新に追いついておらずデグレする羽目になることがあります。(これもおそらくいい方法あります)
  • バイナリ形式と比べるとパフォーマンスに制約が生じる:JSON形式でデータをやり取りするため、gRPCのバイナリ形式と比べてデータサイズが大きくなり、パフォーマンスに制約が生じることがありますが、個人的にはあまり感じたことはありません。

gRPCのメリット・デメリット

メリット

  • 高速かつリアルタイム通信に強い:高速なデータ転送が可能で、リアルタイム通信を必要とするサービス(リアルタイムチャットアプリやオンラインゲームなど)に適しています。
  • 多言語でも型安全な開発が可能:多言語でも自動生成コードによって型の一貫性が保証されます。

デメリット

  • コード自動生成が割と手間:APIの変更があるたびにコードの自動生成が必要なため、フロントエンドの開発者にとっては手間が増えます。
  • 学習コストが高い.protoファイルの読み方、書き方や自動生成したコードの使い方など慣れるまで大変です。
  • ブラウザから直接使用できない:javascriptで使用するためにはgRPC-Webのような追加のライブラリが必要です。

tRPCのメリット・デメリット

メリット

  • 開発体験が良い:TypeScriptを使用したプロジェクトに適しており、コードジャンプ機能や型補完機能によって開発体験が非常に向上します。
  • APIの変更が容易で開発速度が向上:サーバーとクライアントが同じコードベースで型を共有できます。これにより、APIの変更がクライアント側にも即座に反映され開発速度が向上します。

デメリット

  • Typescriptの知見が必要:TypeScript以外の言語では利用できません。
  • ドキュメントが少ない:比較的新しい技術であるため、ツールやライブラリのエコシステムが他と比べて充実していない点があります。

それぞれの採用の決め手

学習コストをかけず、リアルタイム性が求められないアプリケーションで小〜大規模プロジェクト
REST API

高性能なリアルタイム通信が必要で、フロントエンドとバックエンドで異なる言語で型安全に開発を行いたい大規模プロジェクト
gRPC

TypeScriptの知見があり、型安全性と開発速度を重視したい小〜中規模プロジェクト
tRPC

まとめ

今回は、REST API、gRPC、tRPCの特徴、開発の流れ、そして私が個人的に感じたメリットとデメリットを紹介しました。
これまではシンプルさからREST APIを採用することが多かったのですが、TypeScriptでの型管理が面倒であることがネックでした。今後、Next.jsとTypeScriptを使用する私としては、開発速度や開発体験の良さからtRPCを採用していきたいと思っています。

Next.jsでtRPCを含む雛形プロジェクトをすぐに作成できるT3スタックというCLIツールがあるので、これをさらに深掘りしていこうと思います。T3スタックを誰でも簡単に始められるための手順や雛形プロジェクトの中身については、以前の記事にまとめたので、気になる方はぜひご覧ください!
https://zenn.dev/kiwichan101kg/articles/279cc65988a39b

以上、主観強めにはなりましたが、API設計のアーキテクチャスタイルの選択に悩んでいる方に少しでも参考になれば幸いです!

Discussion

TomoTomo

めっちゃ参考になります。良質な記事をありがとうございます。

kiwichankiwichan

とても励みになります!ありがとうございます!🙌