gRPCのハローワールドをやってみた(Go)
最初に
gRPCのハローワールドをやってみたので備忘録です。
内容はクライアントからPin
を渡すと、サーバーからPon!
が返ってくるだけの実装です。
ちなみに当方Goは初心者です。
% go run client/main.go Pin
Pon!
環境
チップ | OS | Go |
---|---|---|
Apple M1 Pro | macOS Monterey | go1.18 darwin/arm64 |
gRPCの特徴
- Googleが2015年に開発したオープンソース
- いろんな環境で実行できるRPC(Remote Procedure Call)のフレームワーク
-
protocol buffersをIDL(インターフェイス定義言語)として使用
- Googleが2008年に開発したオープンソースのスキーマ言語
- 11言語に対応
- HTTP2で通信
- gRPCのgはGoogleのgではない。リリースごとに意味が違う。
開発のおおまかな流れ
- protoファイルを作成する(プロトコルバッファの定義)
- protoファイルをコンパイルしてサーバー、クライアントの雛形のコードを生成する
- 雛形のコードを利用してサーバーとクライアントを実装する
実際にやったプロジェクトのファイル構成。
📦hello-grpc-go
┣ 📂client
┃ ┗ 📜main.go
┣ 📂go-protocol-buffer
┃ ┣ 📜pin-pon.pb.go
┃ ┗ 📜pin-pon_grpc.pb.go
┣ 📂proto
┃ ┗ 📜pin-pon.proto
┣ 📂server
┃ ┗ 📜main.go
┣ 📜go.mod
┗ 📜go.sum
環境構築
公式のガイド
Goをインストール
インストール方法はいろいろあるので好きなやり方で入れておきます。
一応公式のリンクだけ載せておきます。
Protocol Buffersのコンパイラをインストール
Homebrewでインストール
brew install protobuf
バージョンを確認
% protoc --version
libprotoc 3.19.4
プロトコルバッファをコンパイルするためのGo用のプラグインをインストールする
-
protoc-gen-go
Goのプロトコルバッファパッケージを生成するprotocプラグイン。 -
protoc-gen-go-grpc
protoファイルで定義したサービスと連携させるためのGoのコードを生成するプラグイン。
※ protoc
はプロトコルバッファからコードを生成するためのコンパイラのことのようです。
参考: protocプラグインの書き方 > protoc
$ 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
バージョンを確認
% protoc-gen-go --version
protoc-gen-go v1.28.0
% protoc-gen-go-grpc --version
protoc-gen-go-grpc 1.2.0
実装
プロジェクト用のディレクトリを作成
mkdir hello-grpc-go
cd hello-grpc-go
Protocol Buffers の定義
proroファイル用のディレクトリを作成。
mkdir proto
下記の用に定義を作成。
syntax = "proto3";
// プロトファイルの名前空間を定義
package PinPon;
// 変換後のgoファイルを出力するディレクトリかつパッケージ名
option go_package = "./go-protocol-buffer";
message pinPonRequest {
string words = 1; // 数字はフィールド番号(タグ)
};
message pinPonResponse {
string words = 1; // 数字はフィールド番号(タグ)
};
service PinPonService {
rpc send (pinPonRequest) returns (pinPonResponse);
}
以下の箇所はプロトコルバッファの仕様で、シリアライズされたデータから値を特定するために、プロパティに対してフィールド番号(タグ)を指定しています。
string words = 1; // 数字はフィールド番号(タグ)
サービスがRPCのメソッドのまとまりです。サービス内に定義したメソッドをサーバー側で実装し、クライアント側から呼び出して実行します。
Protocol Buffers をコンパイルしてGoの定義ファイルを出力する
protoc -I. --go_out=. --go-grpc_out=. proto/*.proto
-I.
はprotoファイルをimport文で検索するための起点になるディレクトリを指定するオプション。
--go-grpc_out=.
はプロトファイルからgrpcのサーバーとクライアントの雛形を作成するためのオプション。=.
はファイルを出力したいパスを指定します。
実行すると以下のようになります。
📦hello-grpc-go
┣ 📂go-protocol-buffer
┃ ┣ 📜pin-pon.pb.go
┃ ┗ 📜pin-pon_grpc.pb.go
┣ 📂proto
┃ ┗ 📜pin-pon.proto
サーバー側の実装
- 通信の確立
- プロトコルバッファから生成した構造体を使ってサーバーの処理を作成
- gRPCのサーバーを作成
- サービスにgRPCサーバーと実装したサーバーの処理を登録
- サーバーの起動
package main
import (
"context"
"fmt"
go_protocol_buffer "hello-grpc-go/go-protocol-buffer"
"log"
"net"
"google.golang.org/grpc"
)
type server struct {
go_protocol_buffer.UnimplementedPinPonServiceServer
}
func (s *server) Send(ctx context.Context, req *go_protocol_buffer.PinPonRequest) (*go_protocol_buffer.PinPonResponse, error) {
resWords := ""
if req.Words == "Pin" {
resWords = "Pon!"
} else {
resWords = "Please need words 'Pin'!"
}
res := &go_protocol_buffer.PinPonResponse{
Words: resWords,
}
return res, nil
}
func main() {
listener, err := net.Listen("tcp", "localhost:50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
go_protocol_buffer.RegisterPinPonServiceServer(grpcServer, &server{})
fmt.Println("server is runnig...")
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
クライアント側の実装
- grpcのコネクションを確立
- プロトコルバッファから生成したコードを使ってサービスのクライアントを作成
- クライアントを使ってデータを送信
- レスポンスを受け取る
package main
import (
"context"
"fmt"
go_protocol_buffer "hello-grpc-go/go-protocol-buffer"
"log"
"os"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
client := go_protocol_buffer.NewPinPonServiceClient(conn)
callPinPon(client)
}
func callPinPon(client go_protocol_buffer.PinPonServiceClient) {
reqWords := ""
if len(os.Args) >= 2 {
reqWords = os.Args[1]
}
res, err := client.Send(context.Background(), &go_protocol_buffer.PinPonRequest{Words: reqWords})
if err != nil {
log.Fatalln(err)
}
fmt.Println(res.GetWords())
}
実行
サーバー側を起動
go run server/main.go
server is runnig...
クライアント側を実行
コマンドライン引数でPin
を渡すと、Pon!
が返ってきます。
% go run client/main.go Pin
Pon!
コマンドライン引数でPin
以外を渡すと、Please need words 'Pin'!
と返ってきます。
go run client/main.go hoge
Please need words 'Pin'!
最後に
ミニマムの構成で作ってみてgRPCについてどんなものかを体系的に理解することを趣旨としてやってみました。自分なりに用語の整理をしながらやっていってある程度はどういうものかが理解できた気がします。
今回gRPCのキャッチアップでこちらのUdemyの講座をやってみましたが、体系的に情報がまとまっていて環境構築から実装までの手順もわかりやすく個人的にはいい内容でした。
Discussion