gRPCをGoで使う際の使い方をまとめます。だいたい公式ドキュメントを要約して日本語にした感じです。
gRPCとは
もともとGoogleで開発された、あらゆる環境(Go、Python、C++とか)で利用できるhttp/2ベースのリモートプロシージャコールフレームワーク(RPC)です。
C/C++/C#, Dart, Python, Node.js, Ruby, Goなど、いくつかの言語で利用できます。
対応している言語は下記を参照してください。
gRPCの何が嬉しいのか
In gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object, making it easier for you to create distributed applications and services.
ドキュメントでは、gRPCの利点として上記のように述べています。
要約すると、"別のマシン上のサーバアプリケーションのメソッドをローカルのコード内のメソッドのように実行できるため、ロジックを分散させることができるので嬉しい"とのことです。マイクロサービスアーキテクチャでよく用いられるそうです。
REST APIでは、GETリクエストやPOSTリクエストでいい感じにJSONとして情報を詰めて送受信などしますが、gRPCではprotocでコードを自動生成し、シリアル化された情報を送受信します。
また、ストリームでの通信が手軽に書けて、プロトコルバッファを用いた通信を行えるのも利点です。
環境
- OS: Windows 11
- CPU: 12th Gen Intel Core i7-1260P
- Go: go version go1.25.5 windows/amd64
導入
最初にprotocをインストールします。
aptの場合はこれでインストールできます。
apt install -y protobuf-compiler
protoc --version # check
実装例
はじめに、サーバ・クライアントであたかもローカルの関数のように別プロセスの関数を呼んでみます。
サーバ・クライアントで足し算RPC
別々のプロセスであるサーバとクライアント間で通信できることを確かめます。
今回は、サーバに
int a
int b
string userName
を送り、クライアントにはa + bの結果であるresultと、"Hello! " + userNameをgreetingとして返します。
int result
string greeting
上記の内容をプロトコルバッファのデータ構造の定義ファイルとしてmyCalculator.protoファイルに書きます。
syntax = "proto3";
option go_package = "github.com/kanadesisido/myCalculator";
service Calculator {
rpc Add (CalcRequest) returns (CalcReply) {}
/// ...
}
message CalcRequest {
int32 a = 1;
int32 b = 2;
string userName = 3;
}
message CalcReply {
int32 result = 1;
string greeting = 2;
}
最初のsyntax = "proto3"は文法の宣言でとりあえず書きます。
option go_package = "github.com/kanadesisido/myCalculator"はGoのコードを自動生成するために必要です。
モジュール名+生成したコードが置かれるディレクトリパスを指定するようです。1
service Calculatorの部分はサーバ側に用意する関数の定義を書きます。任意個書くことができます。
message CalcRequestは、やり取りするデータの型と名前を指定します。ここで、各フィールドに連番を振る必要があります。0は割り振れません。
詳しいprotoの書き方は以下をご参照ください。
下記を実行してコードを自動生成します。
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./myCalcurator.proto
source_relativeはそのままprotocに相対パスを使うよう指示するフラグで、--go_out=.はカレントディレクトリに作成することを指定します。
--go_out, --go-grpc_outのように、gRPC用にもフラグを渡しているようです。これは、gRPCとプロトコルバッファ(protoc)が別物であることに起因します2
コマンドを実行すると、myCalcurator_grpc.pb.goとmyCalcurator.pb.goが自動生成されます。生成されたmyCalcurator_grpc.pb.goを眺めると、サーバのロジックとして以下のようなコードがあります
type CalculatorServer interface {
Add(context.Context, *CalcRequest) (*CalcReply, error)
mustEmbedUnimplementedCalculatorServer()
}
// UnimplementedCalculatorServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedCalculatorServer struct{}
func (UnimplementedCalculatorServer) Add(context.Context, *CalcRequest) (*CalcReply, error) {
return nil, status.Error(codes.Unimplemented, "method Add not implemented")
}
Unimplementedと書かれているように「実装されていない」structがあります。Serverでは、ここにあるCalcuratorを継承(みたいなことを)したSeverを作成し、Addをオーバライド(みたいなこと)をしてあげる必要があります。
server/main.goを作成し、以下のように書きます。
package grpc_tutorial
import (
"context"
pb "github.com/kanadesisido/grpc-tutorial"
)
type server struct {
pb.UnimplementedCalculatorServer
}
func (s *server) Add(_ context.Context, req *pb.CalcRequest) (*pb.CalcReply, error) {
a := req.GetA()
b := req.GetB()
userName := req.GetUserName()
return &pb.CalcReply{Result: a + b, Greeting: "Hello! " + userName}, nil
}
はじめにserverのstructを作成し、UninplementedCalculatorServerを入れます。(継承みたいなことをする)
そして、そのstructにAddメソッドを生やします。(オーバライドみたいなことをする)
先程protoで宣言した内容はすべて自動生成されたコードの中に反映されています。あとは、main関数を書きます。
package main
import (
"context"
"log"
"net"
pb "github.com/kanadesisido/grpc-tutorial"
"google.golang.org/grpc"
)
type server struct {
pb.UnimplementedCalculatorServer
}
func (s *server) Add(_ context.Context, req *pb.CalcRequest) (*pb.CalcReply, error) {
a := req.GetA()
b := req.GetB()
userName := req.GetUserName()
return &pb.CalcReply{Result: a + b, Greeting: "Hello! " + userName}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterCalculatorServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
これでサーバの準備ができました。次にクライアント(呼び出す側)の準備をします。
はじめに、grpc.NewClientでクライアントを作成します。grpc.WithTransportCredentialsはセキュリティ認証情報のオプション(DialOption)を返します。ここにinsecure.NewCredentials()を渡すとトランスポートセキュリティを無効化することになります。今回は簡単のため、TLSを無効化します。
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
かつてNewClient(...)のかわりにDial(...)が使われていましたが、deprecatedとなり、今は主にNewClient(...)が使われているようです。
Calcuratorのクライアントを作成します。
c := pb.NewCalculatorClient(conn)
実際に遠隔で関数を呼び出すには以下のように書くことができます。
r, err := c.Add(ctx, &pb.CalcRequest{A: 3, B: 5, UserName: "Alice"})
お手軽ですね。
クライアントのコードの全体はこんな感じです。
package main
import (
"context"
"log"
"time"
pb "github.com/kanadesisido/grpc-tutorial"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
// disable TLS for simplicity
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewCalculatorClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// call the Add method
r, err := c.Add(ctx, &pb.CalcRequest{A: 3, B: 5, UserName: "Alice"})
if err != nil {
log.Fatalf("could not add: %v", err)
}
log.Printf("Result: %d, Greeting: %s", r.GetResult(), r.GetGreeting())
}
ターミナルを2つ開き、serverとclientを実行します
cd server
go run main.go
cd client
go run main.go
Terminal_2の出力は
$ go run main.go
2026/03/19 14:33:39 Result: 8, Greeting: Hello! Alice
となります。しっかりと呼び出されているようですね!
※ストリームについては今後加筆します。