はじめに:gRPCとは? RESTと何が違うのか?
マイクロサービスアーキテクチャでは、サービス間の効率的な通信がシステムのパフォーマンスを大きく左右します。これまで主流だったREST (JSON over HTTP/1.1) は、その柔軟性と可読性の高さから広く使われてきましたが、パフォーマンスや型安全性に課題を抱えることもありました。
gRPCは、Googleによって開発された、これらの課題を解決するためのオープンソースのRPC (Remote Procedure Call) フレームワークです。
RESTと比較したgRPCの主な特徴:
- 高いパフォーマンス: デフォルトでHTTP/2を通信プロトコルとして使用します。HTTP/2は、単一のTCP接続上で複数のリクエスト/レスポンスを並行して送受信できる「多重化」や、ヘッダー情報を圧縮する「ヘッダー圧縮」などの機能を持ち、通信効率が大幅に向上します。
- Protocol Buffersによるシリアライズ: メッセージのシリアライズ(バイト列への変換)には、JSONやXMLの代わりに、バイナリベースのProtocol Buffers (Protobuf) を使用します。Protobufは非常にコンパクトで、エンコード・デコードが高速です。
- 厳密なスキーマ定義:
.protoというIDL (Interface Definition Language) ファイルでサービス(APIのメソッドやメッセージの構造)を厳密に定義します。これにより、サーバーとクライアント間での型の不一致を防ぎ、強力な型安全性を実現します。 - 多様な通信パターン: 通常のリクエスト/レスポンス(Unary RPC)だけでなく、サーバーからクライアントへデータをストリーミングする、クライアントからサーバーへストリーミングする、あるいは双方向でストリーミングするといった高度な通信パターンをサポートします。
本記事では、Go言語を使って簡単なgRPCサービス(ユーザー情報を取得するサービス)を実装する手順をゼロから解説します。
最短で課題解決する一冊
この記事の内容と高い親和性が確認できたベストマッチです。早めにチェックしておきましょう。
開発環境のセットアップ
まず、Goの開発環境に加えて、Protocol Buffersコンパイラprotocと、Go用のgRPCプラグインをインストールします。
- Goのインストール: go.dev からインストールします。
- Protocol Buffersコンパイラのインストール: リリースページから、お使いのOSに合った
protocをダウンロードし、パスの通ったディレクトリに配置します。 - Goプラグインのインストール:
インストール後、go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2$GOPATH/bin(または$HOME/go/bin)にパスが通っていることを確認してください。
ステップ1:.protoファイルでサービスを定義する
プロジェクトのルートにprotoディレクトリを作成し、その中にuser.protoというファイルを作成します。このファイルに、サービスのインターフェースを定義します。
// proto/user.proto
syntax = "proto3";
package user;
option go_package = "example.com/grpc-go-guide/proto";
// ユーザーサービス
service UserService {
// ユーザーIDを指定してユーザー情報を取得する
rpc GetUser(GetUserRequest) returns (User);
}
// GetUserメソッドのリクエストメッセージ
message GetUserRequest {
string user_id = 1;
}
// ユーザー情報を表すメッセージ
message User {
string user_id = 1;
string name = 2;
int32 age = 3;
}syntax = "proto3";: 使用するProtobufのバージョンを宣言します。service UserService { ... }:UserServiceという名前のサービスを定義し、その中にGetUserというRPCメソッドを定義します。message User { ... }: メソッドが使用するデータ構造をmessageとして定義します。各フィールドには型と、一意な番号(タグ)を割り当てます。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
ステップ2:Goのコードを自動生成する
次に、protocコマンドを使って、.protoファイルからGoのコードを自動生成します。
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/user.protoこのコマンドを実行すると、protoディレクトリ内に以下の2つのファイルが生成されます。
user.pb.go:UserやGetUserRequestといったメッセージのGoの構造体と、シリアライズ/デシリアライズのためのコードが含まれます。user_grpc.pb.go:UserServiceのインターフェースや、サーバーとクライアントのスタブコード(雛形)が含まれます。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
ステップ3:gRPCサーバーを実装する
自動生成されたコードを使って、gRPCサーバーを実装します。server/main.goを作成します。
// server/main.go
package main
import (
"context"
"log"
"net"
pb "example.com/grpc-go-guide/proto" // 生成されたコードをインポート
"google.golang.org/grpc"
)
// user_grpc.pb.goで定義されたインターフェースを満たすための構造体
type server struct {
pb.UnimplementedUserServiceServer
}
// GetUserメソッドの実装
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
log.Printf("Received request for user: %s", req.GetUserId())
// 本来はデータベースなどからユーザー情報を取得する
// ここではダミーデータを返す
if req.GetUserId() == "1" {
return &pb.User{UserId: "1", Name: "Alice", Age: 30}, nil
}
return nil, status.Errorf(codes.NotFound, "User not found")
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 新しいgRPCサーバーを作成
s := grpc.NewServer()
// サーバーにサービスを登録
pb.RegisterUserServiceServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}pb.UnimplementedUserServiceServerを埋め込むことで、前方互換性を保ちつつ、実装したいメソッドだけを定義できます。GetUserメソッドは、リクエストを受け取り、*pb.Userとerrorを返します。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
ステップ4:gRPCクライアントを実装してサーバーと通信する
最後に、このサーバーにリクエストを送信するクライアントを実装します。client/main.goを作成します。
// client/main.go
package main
import (
"context"
"log"
"time"
pb "example.com/grpc-go-guide/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
// サーバーへの接続を確立
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// 新しいクライアントスタブを作成
c := pb.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// GetUserメソッドを呼び出す
r, err := c.GetUser(ctx, &pb.GetUserRequest{UserId: "1"})
if err != nil {
log.Fatalf("could not get user: %v", err)
}
log.Printf("User Info: ID=%s, Name=%s, Age=%d", r.GetUserId(), r.GetName(), r.GetAge())
}grpc.Dialでサーバーへの接続を作成します。ここでは簡単のため暗号化なし(insecure)で接続しています。pb.NewUserServiceClient(conn)で、サーバーのメソッドを呼び出すためのクライアントを取得します。- あとは、ローカルの関数を呼び出すかのように
c.GetUser(...)を実行するだけです。
まずサーバー(go run server/main.go)を起動し、次に別のターミナルでクライアント(go run client/main.go)を実行すると、クライアントのログにユーザー情報が表示され、サーバーのログにリクエスト受信のメッセージが表示されるはずです。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
まとめ:マイクロサービス通信の強力な選択肢
本記事では、GoとgRPCを使って、サービスを定義し、サーバーとクライアントを実装する一連の流れを解説しました。
.protoファイルでスキーマを最初に定義する「スキーマファースト」な開発により、堅牢なAPIを構築できる。- コード自動生成により、面倒なボイラープレートコードの記述から解放される。
- HTTP/2とProtocol Buffersの恩恵により、高速で効率的な通信が実現できる。
gRPCは、特に内部のマイクロサービス間通信のように、パフォーマンスと信頼性が重視される場面でその真価を発揮します。Go言語のシンプルさとパフォーマンスはgRPCと非常に相性が良く、スケーラブルな分散システムを構築するための強力な基盤となります。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。





