はじめに
今回は「カレー作りで理解するgRPC」というテーマでお届けします。
プログラミングにおいて学びづらい概念や技術を、料理になぞらえて説明するとわかりやすいと感じています。
特にgRPCは、高速・軽量な通信を実現する便利な仕組みですが、最初は「何がすごいのか」「どんな時に使うのか」がイメージしにくいですよね。
そこで、カレー作りの例を使って、より身近にgRPCのポイントを捉えてみましょう!
プログラミングは料理と同じ
私は駆け出しのエンジニアですが、学びの過程で「プログラミングは料理と似ている」と感じることがよくあります。料理をしたことがない人が、いきなり「フランス料理を作って」と言われても難しいように、プログラミングでも最初から複雑なシステムは作れません。
食材の切り方や火加減を学ぶように、プログラミングも基本の構文やロジックを一つひとつ身につけることが大切です。私自身も日々少しずつ積み重ねています。
焦らず、玉ねぎを飴色になるまで炒めるように基礎を丁寧に学んでいけば、やがて自分だけの“味”を出せるようになります。ぜひ一緒に学び、成長していきましょう!
gRPCとは
gRPCは、Googleが開発した高速・軽量なRemote Procedure Call (RPC)フレームワークです。
一言で言えば、「サービス同士が効率よくやり取りをするための通信方法」で、マイクロサービス構成のシステムなど、多数のサービスやサーバが相互に通信し合う場面で頻繁に使われます。
従来のREST APIに比べてバイナリ形式の通信(Protocol Buffers)を使うため、データ交換が高速かつ軽量というメリットがあります。
また、APIの定義を.protoファイルで宣言し、そのファイルを元に自動生成されるコードを使うことで、開発効率が大幅に向上し、異なる言語の間でもスムーズな連携が可能になります。
カレー作りで例えると・・・
-
REST API:情報を伝えるための「手順書」
REST APIは、食材を買いに行くたびに「このスーパーではどこに玉ねぎがあるのか」「あの店舗では牛肉はどこに置いてあるのか」を手順書に沿って探し回るイメージです。文章(HTTPリクエストとレスポンス)によるやり取りなので、比較的わかりやすい反面、どうしても“やり取りの回数”や“データ量”が多くなることがあります。 -
gRPC:決められたレシピと道具を使う、効率重視の「宅配サービス」
必要な材料や数量があらかじめ定義されているため、いちいち細かい手順書を読み解かなくても、必要なものをサクッと取り寄せできるイメージです。
大量のリクエストや複雑なやり取りが必要な場面でも、gRPCは通信コストを抑えつつ高速にデータをやり取りしてくれます。何度もスーパーへ足を運ぶより、まとめて材料を手元に届けてもらうほうが効率的ですよね。
サンプルコード(TypeScript)
ここでは、簡単な実装を通して、「カレー」に必要な材料を返すgRPCの例をTypeScriptで示します。
【前提】
- Node.js環境でgRPC関連モジュールをインストール済み(@grpc/grpc-js, @grpc/proto-loader など)
-
curry.proto
という.protoファイルを用意している
1. curry.protoファイル
syntax = "proto3";
package curry;
service RecipeService {
rpc GetIngredients (IngredientsRequest) returns (IngredientsReply);
}
message IngredientsRequest {
string dish_name = 1;
}
message IngredientsReply {
repeated string ingredients = 1;
}
2. サーバ側実装 (server.ts)
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
import path from "path";
const PROTO_PATH = path.join(__dirname, "curry.proto");
// .protoファイルを読み込み、オブジェクトとして取得
const packageDefinition = protoLoader.loadSync(PROTO_PATH);
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
// curryパッケージとService定義を参照
const curryPackage: any = (protoDescriptor as any).curry;
function getIngredients(call: any, callback: any) {
const dishName = call.request.dish_name.toLowerCase();
if (dishName === "curry") {
callback(null, { ingredients: ["玉ねぎ", "にんじん", "じゃがいも", "牛肉", "カレールー"] });
} else {
callback(null, { ingredients: [] });
}
}
function main() {
const server = new grpc.Server();
// RecipeService の実装を登録
server.addService(curryPackage.RecipeService.service, {
GetIngredients: getIngredients
});
server.bindAsync("0.0.0.0:50051", grpc.ServerCredentials.createInsecure(), () => {
console.log("Server running at http://0.0.0.0:50051");
server.start();
});
}
main();
3. クライアント側実装 (client.ts)
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
import path from "path";
const PROTO_PATH = path.join(__dirname, "curry.proto");
// .protoファイルを読み込み、オブジェクトとして取得
const packageDefinition = protoLoader.loadSync(PROTO_PATH);
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
// curryパッケージとService定義を参照
const curryPackage: any = (protoDescriptor as any).curry;
// クライアント側の処理
function main() {
const client = new curryPackage.RecipeService(
"localhost:50051",
grpc.credentials.createInsecure()
);
const request = { dish_name: "curry" };
client.GetIngredients(request, (err: any, response: any) => {
if (err) {
console.error("Error:", err);
return;
}
console.log("🍛 カレーの材料:", response.ingredients);
});
}
main();
上記の例では、サーバ側が「dish_name が curry なら玉ねぎ、にんじん、じゃがいも、牛肉、カレールーを返す」という非常にシンプルな処理をしています。
クライアントは「curry.proto」の定義を読み込み、RecipeService
に対してリクエストを送るだけで、必要な情報(食材)を高速に手に入れることができます。
このように、複雑な処理を行う場面でもgRPCなら効率的な通信や開発が可能です。
まとめ
gRPCは、サービス間通信を効率化して、データや処理をスピーディーにやり取りできる手段です。
カレー作りの宅配サービスのイメージのように、一度にまとめて必要なものを手元に揃えられるのがgRPCの強みだと思っていただければ、理解しやすいのではないでしょうか。
この記事が、皆さんの理解の手助けになれば幸いです。一緒に理解を深めていきましょう!