サービス間通信技術「gRPC」の基礎知識――「Protocol Buffers」とは?スキマ時間にこっそり学ぶ「gRPC」入門(1)

第1回は、サービス間の通信技術として古くから使われているRPCの解説から入り、gRPCの登場、特徴、サポートされるプラットフォームやプログラミング言語、データ交換フォーマットであるProtocol Buffersのあらましについて紹介します。

» 2022年11月11日 05時00分 公開

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「スキマ時間にこっそり学ぶ「gRPC」入門」のインデックス

連載:スキマ時間にこっそり学ぶ「gRPC」入門

 本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。


gRPCの概要

 Googleによって開発され、オープンソース化された通信技術である「gRPC」は、マイクロサービスアーキテクチャにおけるサービス間の通信手段としてはもとより、モバイルアプリがサービスにアクセスするためのインタフェースとしても注目されています。現在ではLinux Foundation傘下のCloud Native Computing Foundation(CNCF)のもとで開発されています。

 本連載では、gRPCについて、さまざまなプログラミング言語によるアプリケーションの実装を通じて、技術的な仕様や開発手法を紹介していきます。

RPCとは?

 gRPCは、RPCという通信プロトコルです。RPCとはRemote Procedure Callの略で、日本語にすると「遠隔手続き呼び出し」となります。RPCは、他の多くのプロトコルと同様にクライアント/サーバ型のモデルを採用しています。サーバに用意された、サービスのための手続き(関数)をクライアントがパラメーターとともに呼び出し、クライアントはその結果を受け取ります(図1)。

 RPC自体は、決して新しい技術ではなく、考え方自体はインターネット(TCP/IP)の登場以前からありました。例えば、Sun Microsystems(現Oracle)の開発したRPCの実装であるSunRPCです。1980年代には情報共有のためのNIS(Network Information Service)やファイル共有のためのNFS(Network File System)が、C言語のライブラリとして実装されていました。開発者はあたかも自コンピュータ内における手続き呼び出しと同じ感覚で、外部のコンピュータのサービスを利用できました。

図1 RPC(Remote Procedure Call)

 RPCの実装は上記のSunRPCをはじめとしてさまざまあり、時代とともに変化してきました。近年では、標準的なプロトコルであるHTTPやHTTPSを通信路に使い、XML形式やJSON形式のデータをやりとりするというXML-RPCやJSON-RPCがよく使われていました。標準的な技術を使用するため導入しやすいというメリットがありますが、反面、以下のようなデメリットが指摘されていました。

  • XML/JSONというテキストベースのやりとりになるので伝送効率が悪い
  • テキストフォーマットなのでバイナリデータは扱いにくい
  • HTTP/HTTPSでのやりとりになるので通信路のオーバーヘッドが大きい

 このような問題点を解消するために登場したのが、gRPCです。

gRPCとは?

 gRPCのベースとなる技術はGoogleにより開発されました。「Stubby」と呼ばれたその技術は、2000年代初頭からGoogle内部で運用されてきましたが、独自の仕様が多く、長らく外部での利用は想定されていませんでした。しかし、2015年以降、仕様の一部はHTTP/2によって置き換えられるなどして、GoogleはStubbyをgRPCとしてオープンソース化し、誰でも利用できるようにしています。

 gRPCでは、通信路にはHTTP/2が、データのやり取りにはProtocol BuffersというGoogleの開発した技術が用いられています。Protocol Buffersは、バイナリデータを効率的に扱うことができるのが特徴で、.protoファイルというテキストファイルを定義に用いることで、異なるプログラミング言語との連携を容易にしています。また、HTTP/2を採用することで、オーバーヘッドの少ない通信や、双方向の通信が可能となっています。

 以下に、gRPCの主な特徴をまとめておきます。

  • HTTP/2を使うので通信路のオーバーヘッドを抑えられる
  • バイナリデータを効率よく伝送できる
  • 同期/非同期の通信が可能
  • Unary(1リクエストに1レスポンス)通信に加えて、3つのストリーミング方式(クライアント、サーバ、双方向)による通信が可能
  • サーバ、クライアントともにプログラミング言語を問わない独立した運用が可能
図2 gRPCの実行モデル

 gRPCでは、サーバ側にはgRPCサーバが、クライアント側にはgRPCスタブ(gRPCクライアント)が存在し、プログラミング言語からはそれぞれを呼び出します。gRPCサーバとgRPCスタブの間の通信はプログラミング言語やプラットフォームに依存しないので、異なる言語やプラットフォーム間での運用が可能になっています。

 gRPCではHTTP/2やProtocol Buffersを標準で使用しますが、これらは置き換えることができます。本稿では、標準であるHTTP/2とProtocol Buffersを使っていきます。なお、gRPCの利用に必要な情報は以下の公式サイトから得られます。本稿では、その中から次回以降の理解に必要となる部分を抜粋して紹介していきます。

gRPC

サポートされる環境

 まず、サポートされる環境(プログラミング言語、プラットフォーム、コンパイラ/SDK)を押さえておきましょう。本稿の作成時点で公式にサポートされている環境は表1の通りです。

言語 プラットフォーム コンパイラ/SDK
C/C++ Linux, macOS GCC 6.3以降、Clang 6以降
C/C++ Windows 10以降 Visual Studio 2017以降
C# Linux, macOS .NET Core、Mono 4以降
C# Windows 10以降 .NET Core、.NET 4.5以降
Dart Windows, Linux, macOS Dart 2.12以降
Go Windows, Linux, macOS Go 1.13以降
Java Windows, Linux, macOS Java 8以降(KitKat以降 for Android)
Kotlin Windows, Linux, macOS Kotlin 1.3以降
Node.js Windows, Linux, macOS Node v8以降
Objective-C macOS 10.10以降、iOS 9.0以降 Xcode 12以降
PHP Linux, macOS PHP 7.0以降
Python Windows, Linux, macOS Python 3.5以降
Ruby Windows, Linux, macOS Ruby 2.3以降
表1 gRPCでサポートされているプラットフォームとプログラミング言語

 ご覧の通り、現時点で広く利用されているプログラミング言語がサポートされ、基本的なプラットフォームがサポートされています。特に利用における制約はないと思われます。

アプリケーション開発の流れ

 本稿ではgRPCの概要を述べますが、次回からは各種プログラミング言語を用いたアプリケーション開発の手順を紹介していきます。ここではまず、それに共通する開発の大まかな流れを紹介しておきます。具体的な作業は、各回にて紹介していきます。

 アプリケーション開発の大まかな流れは以下の通りです。サーバとクライアントでほぼ共通となります(図3)。

  1. ツールやライブラリのインストール
  2. サービスの定義
  3. コードの生成
図3 アプリケーション開発の流れ

 まず、プラットフォームやプログラミング言語に依存したツールやライブラリをインストールする必要があります。多くの場合、プログラミング言語に用意されたパッケージマネジャー等を用いて簡単にインストールできます。ツールの名前はgrpc-tools、ライブラリの名前はgrpcとなっていることが多くなっています。

 次に、サービスを定義します。サービスとは、(g)RPCにおける機能そのもので、サービスの名称、含まれる手続き、手続きへのパラメーター、手続きからの戻り値などを定義します。また、やりとりされるデータをメッセージと呼び、パラメーターや戻り値はメッセージとして定義します。いずれも、Protocol Buffersに基づいたフォーマットで定義します。

 最後に、サービス定義に基づきプログラミング言語に応じたコード(クラスなどの定義が含まれる)をツールに含まれるprotocによって生成します。このコードとライブラリを使って、アプリケーションごとの機能を実装していきます。

Protocol Buffers

 既述の通り、gRPCにおけるデータのやりとりは、Protocol Buffersという技術に基づきます。Protocol Buffersをプログラミング言語から使うときに、利用できるサービス(手続き)の定義と、やりとりするデータであるメッセージの定義を、テキストファイルに記述していきます。このテキストファイルは、公式ドキュメントでは.protoファイルと記載されていますが、本稿ではProtocol Buffers定義ファイル(簡略化して定義ファイル)と呼ぶことにします。

 gRPCを使ってサービスを定義し、何かデータをやりとりしようと考えたら、まずはこの定義ファイルの記述から始めます。といっても難しいところは何もなく、プログラミング言語における関数プロトタイプや構造体、クラスを定義するのと同じ要領で、サービス(手続き)の書式を記述し、データのまとまりやデータそのものに名前とデータ型を指定していけばよいのです。

【補足】シリアライズとデシリアライズ

 Protocol Buffersは、gRPCサーバとクライアントの間でのやりとりを規定しますが、基本的にはバイト列でのやりとりです。プログラミング言語で使用するオブジェクトなどの複雑に構造化されたデータを、単一のバイト列や文字列に変換することをシリアライズ(直列化)と呼びます。逆に、バイト列や文字列から構造化されたデータを取得することはデシリアライズと呼びます。gRPCでは、シリアライズとデシリアライズはライブラリによって自動化されているので、開発者がこれを意識する必要はほとんどありません。

基本的なルール

 定義ファイルを記述する上での基本的なルールは、以下の通りです。

  • ファイル名はスネークケース(英小文字、単語間はアンダースコア( _ ))を推奨
  • 1行は80文字まで
  • インデントは空白2文字
  • 文字列は二重引用符( " )で囲む

 このほか、記述順序の推奨マナーがありますが、最初はそれほど意識する必要はないでしょう。必要な場合には、都度触れていくことにします。

Protocol Buffers定義ファイル

 定義ファイルは、拡張子を.protoとして作成することになっています。ここからは、書籍情報を扱うBookサービスのためのbooks.protoファイルを作成してみましょう。

【補足】VSCodeで定義ファイルを編集

 定義ファイルはテキストファイルなので、どのようなテキストエディタを使って編集しても問題ありませんが、まだまだ一般的ではないのでサポートされるテキストエディタは限定されてきます。

 ですので、筆者はVSCodeを推奨します。VSCodeでは、vscode-proto3などのProtocol Buffersをサポートする拡張機能が多数リリースされていますので、それらをインストールすることで、シンタックスハイライトやコード補完、スニペットなどの機能を利用できるようになります。

Protocol Buffersバージョンの指定

 Protocol Buffersには、本稿の作成時点でバージョン2とバージョン3が存在します。デフォルトはバージョン2ですが、より単純な構文で多くの機能を持ち、多くのプログラミング言語をサポートするバージョン3の使用が推奨されています。

 そこで、定義ファイルを記述するにあたり、バージョン3で記述されていることを明示しておきます。これには、ファイルの冒頭でsyntaxを定義します。バージョン3なので「proto3」を、syntaxに設定します(バージョン2なら「proto2」)。以下は、Protocol Buffersのバージョンを3に指定しています。

// バージョンを3に指定
syntax = "proto3";
book.proto(以降はファイル名を省略)

 なお、上記の通り定義ファイルにはコメントを記述できます。コメントは、ダブルスラッシュ(//)による1行コメントと、/* 〜 */によるC言語ライクなブロックコメントが使用できます。

 Protocol Buffersバージョン3におけるガイドが以下にありますから、詳細が必要な場合には参照してください。

Language Guide (proto3) | Protocol Buffers | Google Developers

メッセージのフォーマット

 Protocol Buffersでやりとりされるデータを、メッセージと呼びます。メッセージは、データそのもののフォーマットであるほか、サービスを呼び出す際のパラメーターや戻り値のフォーマットともなります。

 メッセージ(MessageName)は複数のフィールド(field_name)を持ち、それぞれのフィールドは型(type)と番号(number)を持ちます。以下のような形式になります。

message MessageName {
  type field_name = number;
  :
}
メッセージ定義の書式

 中括弧({ })を使った、よくある形式ですから、戸惑うこともないでしょう。この書式を用いて、書籍情報そのものを表すBookというメッセージを定義すると以下のようになります。

message Book {		(1)
  int32 id = 1;		(2)
  string title = 2;
  string author = 3;
  string publisher = 4;
  string isbn = 5;
  uint32 price = 6;
}

 (1)では、Bookメッセージを定義するブロックを開始しています。(2)以降は、個々のフィールドの定義です。「int32」が型であり、「id」がフィールド名、「1」がフィールド番号です。

 メッセージ名の制約は特にありませんが、キャメルケース(単語の先頭も大文字にするアッパーキャメルケースあるいはPascalケース)が推奨されています。これは、後述する列挙体でも同様です。

 フィールドの型は、表2に示すものが指定できます。デフォルト値と、C++における型名の対応も例示しました。

型名 概要 デフォルト 例:C++での型名
int32 符号あり32bit整数 0 int32
int64 符号あり64bit整数 0 int64
uint32 符号なし32bit整数 0 uint32
uint64 符号なし64bit整数 0 uint64
sint32 符号あり32bit整数 0 int32
sint64 符号あり64bit整数 0 int64
fixed32 符号なし32bit整数(固定バイト幅) 0 uint32
fixed64 符号なし64bit整数(固定バイト幅) 0 uint64
sfixed32 符号あり32bit整数(固定バイト幅) 0 int32
sfixed64 符号あり64bit整数(固定バイト幅) 0 int64
float 単精度浮動小数点数(32bit) 0.0 float
double 倍精度浮動小数点数(64bit) 0.0 double
bool ブール型(true/false) false bool
string UTF-8もしくは7bitASCII文字列 空文字列 string
bytes バイト列 空のバイト列 string
表2 フィールドの型

 表2を見ると、整数型が細かく設けられています。「int32」「uint32」といった整数はいいとして、「sint32」「fixed32」などはこれらとどう違うのでしょうか? sint32は、int32よりも負数のエンコーディング効率に優れており、sfixed32はint32よりも大きな範囲の数をエンコーディングできる、という違いがあります。詳細は、下記から確認できます。

Encoding | Protocol Buffers | Google Developers

 フィールド名の制約は特にありませんが、スネークケース(基本的に小文字で、単語をアンダースコア(_)で連結する)が推奨されています。これは、後述する列挙体の列挙子でも同様です。

 フィールド番号は、1〜536,870,911(2の29乗-1)の任意の数で、1つのメッセージ中で一意(ユニーク)である必要があります。連続している必要はありませんし、小さい数から使う必要もありません。一意であれば問題ありません。

 なお、型の前に「repeated」キーワードを付加すると、そのフィールドは値を複数持つ、すなわち配列のように振る舞うことを指定できます。以下の例では、authorフィールドが複数のstring型の値を持つことを指定しています。

message Book {
  …略…
  repeated string author = 3;
  …略…
}

列挙型の使用

 プロトコル定義ファイルには、列挙型(enum)も記述できます。列挙型を使うことで、単なる数値型を使うのではなく、意味のある値をフィールドに持たせることができます。

 列挙型の定義は、以下のようになります。

enum EnumName {
  field_name = number;
  :
}
列挙型定義の書式

 形式はメッセージとほとんど同じですが、フィールドの型を指定する必要がないことに注意してください。以下は、Bookメッセージに列挙型BookTypeのtypeフィールドを設ける例です。

enum BookType {		(1)
  BOOK = 0;		(2)
  PAPERBACK = 1;
  POCKET = 2;
  MOOK = 3;
  OTHER = 99;
}
message Book {
  …略…
  BookType type = 7;	(3)
}

 (1)では、BookType列挙型を定義するブロックを開始しています。(2)以降では、個々のフィールドを定義していますが、フィールド番号に0を指定しているものがあることに注意してください。フィールド番号の制約についてはメッセージと同様ですが、列挙型では0を指定できます。0をフィールド番号に持つ列挙子は、メッセージにおけるデフォルト値となります。(3)では、BookメッセージにてBookType列挙体のフィールドを定義しています。

 ここでは、BookType列挙体に、BOOK(単行本)、PAPERBACK(新書)、POCKET(文庫)、MOOK(ムック)、OTHER(その他)を列挙子として持たせ、このうちBOOKをデフォルトとしています。

メッセージをフィールドとする

 メッセージのフィールドには、定義済みのメッセージも指定することができます。つまり、メッセージの入れ子が可能です。レスポンスが、データ本体以外にステータスコードなどを含むといった場合、レスポンス全体をメッセージとして定義して、その中にデータそのものを含ませるといったことができます。

 以下は、検索結果を表すSearchResponseメッセージに、書籍そのもののデータであるBookメッセージをステータスコードとともに持たせる例です。

message SearchResponse {	(1)
  int32 status_code = 1;
  Book book = 2;	(2)
}

 (1)は、Bookメッセージを含むSearchResponseメッセージの定義です。Bookメッセージのフィールドを、(2)でSearchResponseメッセージ中に定義しています。

 なお、メッセージと列挙体は入れ子にもできます。ここまでの例だと、Bookメッセージの内部でBookType列挙体、BookSizeメッセージを以下のように記述できます。

message Book {
  …略…
  enum BookType {
    BOOK = 0;
    …略…
  }
  BookType type = 7;
  message BookSize {
    …略…
  }
  BookSize size = 8;
}

サービスの定義

 サービスの定義は、サービスの名称とその手続き、引数と戻り値を指定します。

service BookService {		(1)
  rpc Search(SearchRequest) returns (SearchResponse);	(2)
}

 (1)は、BookServiceという書籍情報のためのサービスを定義しています。(2)は個々の手続きの定義で、「rpc」から始めます。「Search」は検索を実行する手続きの名前であり、引数にSearchRequestメッセージを渡し、結果としてSearchResponseメッセージを受け取ることを表しています。

パッケージ

 最後に、パッケージを紹介します。パッケージとは、メッセージなどに名前空間を付与して、名称の衝突を回避するための仕組みです。

package AtmarkIt;	(1)
message Book {		(2)
  …略…
}
…略…
message SearchResponse {
  …略…
  AtmarkIt.Book book = 2;	(3)
}

 (1)は、AtmarkItというパッケージを宣言しています。通常、パッケージは定義ファイルの冒頭で宣言し、以降に現れるメッセージ等の定義がパッケージに含まれることを示します。つまり、(2)のメッセージ定義はAtmarkItパッケージの配下に含まれることになります。そして、そのメッセージは(3)のようにドット(.)で区切ってパッケージ名も付記することで参照することができます。

 パッケージの指定は、プログラミング言語ごとに生成されるコードに名前空間やモジュール、パッケージといった形で影響を与えることがありますが、これについては個別の回で触れることにします。

まとめ

 今回は、連載第1回として、gRPCとそれのベースとなるRPCの概要、そしてgRPCを使う上での基本となるProtocol Buffersについて紹介しました。

 次回は、.NET環境とC#でgRPCアプリケーションを作成し、実践に踏み込んでいきます。

筆者紹介

WINGSプロジェクト

有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。

サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/
RSS(https://wings.msn.to/contents/rss.php
Twitter: @yyamada(https://twitter.com/yyamada
Facebook(https://www.facebook.com/WINGSProject


Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。