🚄

gRPC触ってみた

2024/03/04に公開

gRPCの学習をしたので、振り返り用に共有しておきます。
https://grpc.io/
ディレクトリ構成などは、以下のリポジトリを参考にしてください。
https://github.com/Watson-Sei/test-grpc

gRPCとREST APIの違いについて

gRPCとREST APIは、システム間でデータを交換するための2つの異なる通信プロトコルです。以下に主な違いを簡潔に記載します。

  1. プロトコル
    ・ REST APIはHTTP/1.1プロトコルを使用して通信し、主にWebサービスのために設計されている。
    ・ gRPCはHTTP/2を基盤とし、低遅延、高スループットの通信を実現するために設計されている。
  2. データフォーマット
    ・ REST APIでは、主にJSONまたはXML形式のテキストデータを使用している。
    gRPCでは、バイナリ形式のプロトコルバッファ(ProtoBuf)を使用するため、データのシリアライゼーションとデシリアライゼーションが高速である。
  3. API設計
    REST APIはリソース指向であり、HTTPメソッド(GET、POST、PUT、DELETEなど)を使用してリソースの状態を操作します。
    gRPCはサービス指向で、メソッド呼び出しをリモートプロシージャコール(RPC)として定義します。これにより、クライアントとサーバー間でのメソッド実行が直接的に行える。

事前準備

  1. 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
  1. PATHを更新して、protocコンパイラーがプラグインを見つけられるようにする
export PATH="$PATH:$(go env GOPATH)/bin"
  1. protocコマンドを使用するためにprotobufの依存をインストール
brew install protobuf
  1. curlのようにgRPCにリクエストするためにgrpcurlをインストール
brew install grpcurl

Protoファイルの自動生成

hello.protoという名前のprotoファイルを作成します。
スキーマ定義については、以下の記事を参考にしました。
https://qiita.com/Captain_Blue/items/b7a1f4a42f48559fac0c
https://zenn.dev/shuuuuuun/articles/7d292005af7f65

// プロトコルバッファの構文バージョンを指定
syntax = "proto3";

// パッケージの定義
package hello;

// importの定義
// 他の.protoファイルで定義したメッセージ型を使いたい場合に使う
// import "google/protobuf/timestamp.proto";

// 生成されるGo言語のコードが属するパッケージを指定
option go_package = "/pb";

// gRPCサービス
service HelloService {
    // サービス内のRPCメソッドを定義
    rpc SayHello(HelloServiceRequest) returns (HelloServiceResponse) {}
}

message HelloServiceRequest {
    string name = 1;
}

message HelloServiceResponse {
    string message = 1;
}

protoファイルをgoファイルに変換します。
go_outオプションはリクエスト/レスポンス部分のコードの出力先を、go-grpc_outオプションはサービス部分のコード出力先を指定

protoc --go_out=. --go-grpc_out=require_unimplemented_servers=false:. ./proto/hello.proto

サーバー側実装

package main

import (
	"context"
	"fmt"
	"log"
	"net"

	"github.com/Watson-Sei/test-grpc/pb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

type Server struct {
	pb.HelloServiceServer
}

func (s *Server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
	log.Print("Hello " + req.Name)
	return &pb.HelloResponse{
		Message: "Hello " + req.Name,
	}, nil
}

func main() {
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 50051))
	if err != nil {
		panic(err)
	}

	server := grpc.NewServer()

	pb.RegisterHelloServiceServer(server, &Server{})

	server.Serve(lis)

}

クライアント側実装

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Watson-Sei/test-grpc/pb"
	"google.golang.org/grpc"
)

func main() {
	opts := grpc.WithInsecure()
	cc, err := grpc.Dial("localhost:50051", opts)
	if err != nil {
		log.Fatal(err)
	}
	defer cc.Close()

	client := pb.NewHelloServiceClient(cc)
	request := &pb.HelloRequest{Name: "Watson Sei"}

	res, err := client.SayHello(context.Background(), request)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Receive response => %s ", res.Message)
}

gRPCurlでメソッドを呼び出す

サービス一覧を表示する
このコマンドの実行結果は、サーバーで利用可能なgRPCサービスの名前をリストアップします。この例では、Helloという名前のサービスと、gRPCサーバーのリフレクション機能を提供する。

サーバーリフレクションについて
  • -plaintext:セキュリティで保護されていない接続(TLSを使用しない接続)を示します。これは、開発やテスト環境で適している。
  • -import-path .:プロトコルファイル(.protoファイル)が現在のディレクトリにあることを指定します。これにより、grpcurlあh指定されたディレクトリから必要なプロトコル定義を読み込みます。
  • -proto hello.proto:使用するプロトコルファイルの名前を指定します。
  • localhost:50051:接続先のgRPCサーバーのアドレスとポートを指定
  • list:サーバーで利用可能なgRPCサービスのリストを表示するコマンド
grpcurl -plaintext -impor
t-path . -proto proto/hello.proto localhost:50051 list

>> hello.HelloService <- サービスが出力される

その他のgrpcurl操作については、以下の記事を参考にしてください。
https://zenn.dev/miyazi777/articles/9cc47b325b950e45cd16

サーバー側には以下を追記してください。

import "google.golang.org/grpc/reflection"

func main() {
    // サーバーリフレクション設定
    reflection.Register(server)
}

まとめ

gRPCを実際に少し使ってみた感じ、学習にかなりの時間を要するため少し採用に戸惑ってしまいますが使いこなせれば急変するAPI仕様への厳格な開発が行えるのではないかと感じました。
とはいえ、プロトコルファイルの書き方は慣れないものではあります。

Discussion