1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

gRPCをGoで始める:gRPCは何が嬉しいのか,簡単な実装例

1
Posted at

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の場合はこれでインストールできます。

bash
apt install -y protobuf-compiler
protoc --version # check

実装例

はじめに、サーバ・クライアントであたかもローカルの関数のように別プロセスの関数を呼んでみます。

サーバ・クライアントで足し算RPC

別々のプロセスであるサーバとクライアント間で通信できることを確かめます。
今回は、サーバに

int a
int b
string userName

を送り、クライアントにはa + bの結果であるresultと、"Hello! " + userNamegreetingとして返します。

int result
string greeting

上記の内容をプロトコルバッファのデータ構造の定義ファイルとしてmyCalculator.protoファイルに書きます。

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の書き方は以下をご参照ください。

下記を実行してコードを自動生成します。

bash
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.gomyCalcurator.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を作成し、以下のように書きます。

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関数を書きます。

server/main.go
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"})

お手軽ですね。

クライアントのコードの全体はこんな感じです。

client/main.go
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を実行します

Terminal_1
cd server
go run main.go
Terminal_2
cd client
go run main.go

Terminal_2の出力は

$ go run main.go
2026/03/19 14:33:39 Result: 8, Greeting: Hello! Alice

となります。しっかりと呼び出されているようですね!


※ストリームについては今後加筆します。

  1. 参照:https://qiita.com/ko-suke2020/items/5fcfbc98f320b6705e37

  2. 参照:https://stackoverflow.com/questions/70731053/protoc-go-opt-paths-source-relative-vs-go-grpc-opt-paths-source-relative

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?