はじめに
この記事は、海外サイトhttps://tutorialedge.net/golang/go-grpc-beginners-tutorial/
の内容をベースに一部修正を加えたものです。この記事を読み進めることでGoでシンプルなgRPCクライアントとサーバーを構築することができるようになります。
※gRPC公式より抜粋(また、以下の説明でも一部参考にしています。)
gRPCとは?
gRPCはGoogleによって開発されたRPCフレームワークです。
RPCはRemote Procedure Callの略で、逐語的に訳すと「遠隔手続呼び出し」となります。これはすなわち、「あるプログラムがネットワーク上の異なる場所に配置されたプログラムを呼び出して実行すること」と読み取れます。
公式による定義
サービス定義
多くのRPCシステムと同様に、gRPCはサービスを定義するという考えに基づいており、パラメーターと戻り値の型を使用してリモートで呼び出すことができるメソッドを指定します。デフォルトでは、gRPCはプロトコルバッファを使用しますサービスインターフェイスとペイロードメッセージの構造の両方を記述するためのインターフェイス定義言語(IDL)として。必要に応じて、他の代替手段を使用することができます。
gRPCとRESTの違い
WebAPIでよく使われるRESTも、離れたサーバーの機能を呼び出すという点で、RPCと同じ役割で使われるように思えます。しかし、RESTは「リソース志向を強く打ち出す」という思想的な違いがあります。リソース志向とはオブジェクト(リソース)を中心に考え、これに対してHTTPメソッドで操作していく考え方です。RPCではメソッドの呼び出しが基点になり、データはあくまでその副産物であるので考え方は逆になります。この志向性の違いから、RESTとRPCは対照的に捉えられています。
参考記事 ⇨ REST APIの設計で消耗している感じたときのgRPC入門
Migrating APIs from REST to gRPC at WePay
gRPC vs. REST: How Does gRPC Compare with Traditional REST APIs?
RESTと比較した場合のgRPCの長所
gRPCの優れた点として、大きく3つが挙げられます。
-
HTTP/2による高パフォーマンス通信
-
Protocol Buffersによるデータ転送
-
柔軟なライフサイクル
以下、順番に説明していきます。
※また、スターティングgRPC (技術の泉シリーズ(NextPublishing)を一部参考にしております
HTTP/2による高パフォーマンス通信
HTTP/2では通信時にデータがテキストではなくバイナリにシリアライズされて送られます。そのため小さな容量で転送でき、ネットワーク内のリソースをより効率的に使用することができます。また、HTTP/2では一つのコネクションで複数のリクエスト/レスポンスをやりとりできるので、リクエストのたびに接続と切断を行うことがない効率的な通信になります。
高速な通信ができる点において、gRPCはマイクロサービスの内部APIの通信規格として評価されています。
Protocol Buffersによるデータ転送
gRPCではProtocol Buffersのフォーマットにシリアライズしてデータをやり取りします。シリアライザ自体はカスタマイズすることでJSONなどのレスポンスに変えることもできます ※公式
Protocol Buffersの一番の特徴は.protoファイルというIDL(インターフェース記述言語)です。.protoファイルを書いて、コンパイラを実行すると任意の言語のサーバー/クライアント用コードを生成してくれます。わざわざ自分でAPIインターフェースを実装したり、シリアライズされたデータのエンコード/デコード処理を書く必要がありません。
このためgRPCで開発する場合はまず、スキーマを書く必要があります。gRPCではスキーマが最初に書かれるので.protoファイルを見ればAPIの使用は常に明確です。そのため、必然的にスキーマファーストの開発を行うことになります。
※gRPC公式より抜粋
柔軟なライフサイクル
gRPCではAPIで一般的である一つのリクエストに対してレスポンスを一つ送る形式以外に、単方向、双方向のストリーミングRPCに対応しています。
Unary RPC
クライアントから送られた一つのリクエストに対して、レスポンスを一つ返して終了する、最も一般的な形式です。
例
rpc SayHello(HelloRequest) returns (HelloResponse);
Server Streaming RPC
クライアントから送られたリクエストに対して、レスポンスを複数回に分けて返します。時間のかかる処理について、非同期的にレスポンスを返すことができます。
用途として、サーバーから任意のタイミングでクライアントに通知をさせたい時に使用します。具体例を挙げると、お知らせ通知やタイムラインのリアルタイム更新などです。
サーバー側の状態変化をクライアントで検知したい場合にストリーミングがないと、定期的な通信をクライアントが行う必要があり、無駄な通信が多く発生してしまいます。ServerStreamingRPCを使えば、サーバー側から状態変化時にクライアントに直接伝えることができるのでネットワーク負荷を最小にすることができます。
例
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
Client Streaming RPC
クライアントからリクエストを分割してサーバーに送り、サーバーが全てリクエストを受け取ってからレスポンスを返します。大きなデータアップロードなどに用いられます。
例
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
Bidirectional Streaming RPC
任意のタイミングでどちらからもリクエストレスポンスを送ることができます。ユースケースとして、チャットやオンラインゲームになります。REST APIではストリーミングが困難なのでWEBSocketサーバーを立てる必要がありましたが、gRPCの場合、単一のサーバーで双方向通信が必要なAPIとしても対応可能です。
例
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
ハンズオン!(Golang)
サンプルリポジトリ(完成形)
前提
- Goがインストールされている(Go 1.6 or higher )
実践
grpcのインストール
go get -u google.golang.org/grpc
Protocol Buffers v3 をインストール。
brew install protobuf
Protocol Buffers の Go プラグインをインストール。
go get -u github.com/golang/protobuf/protoc-gen-go
go mod init の実行(必要であれば)
go mod init github.com/tutorialedge/go-grpc-beginners-tutorial
準備完了です!
以下実装に入っていきます!!
全体構成(最終)
- Server.goの作成
package main
import (
"log"
"net"
"google.golang.org/grpc"
)
func main() {
lis, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %s", err)
}
}
このファイルではgrpcパッケージをインポートして、新規grpcサーバーを作成し、tcp接続を行なっています。
grpcServer := grpc.NewServer()
- Chat.protoの作成
syntax = "proto3";
package chat;
message Message {
string body = 1;
}
service ChatService {
rpc SayHello(Message) returns (Message) {}
}
この.protoファイルは任意の言語で記述された任意のgRPCクライアントから呼び出すことができる、単独のSayHello関数を公開しています。
- protocを使ってGo固有のgRPCコードの生成
protoc --go_out=plugins=grpc:chat chat.proto
これにより、chat/chat.pb.goが生成されたかと思います。もし生成されないようでしたら、chatフォルダがあるかどうか、goのパスが通っている確認してください。
また、server.goでChatServiceを登録する処理を追加します。
- ChatServiceの追加(server.go)
package main
import (
"fmt"
"log"
"net"
"github.com/tutorialedge/go-grpc-beginners-tutorial/chat"
"google.golang.org/grpc"
)
func main() {
fmt.Println("Go gRPC Beginners Tutorial!")
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 9000))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := chat.Server{}
grpcServer := grpc.NewServer()
chat.RegisterChatServiceServer(grpcServer, &s)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %s", err)
}
}
次に、SayHelloを取り込んでMessage本文を読み取って返すメソッドを定義します。
- Chat/chat.goの追加
package chat
import (
"log"
"golang.org/x/net/context"
)
type Server struct {
}
func (s *Server) SayHello(ctx context.Context, in *Message) (*Message, error) {
log.Printf("Receive message body from client: %s", in.Body)
return &Message{Body: "Hello From the Server!"}, nil
}
ここまで終わったらサーバーを実行してみます。
go run server.go
Go gRPC Beginners Tutorial!
gRPCサーバーが起動しました!
GoでのgRPCクライアントの構築
サーバーが稼働しているので、サーバーと通信するシンプルなクライアントを構築しましょう。
- Client.go
package main
import (
"log"
"golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/tutorialedge/go-grpc-beginners-tutorial/chat"
)
func main() {
var conn *grpc.ClientConn
conn, err := grpc.Dial(":9000", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %s", err)
}
defer conn.Close()
c := chat.NewChatServiceClient(conn)
response, err := c.SayHello(context.Background(), &chat.Message{Body: "Hello From Client!"})
if err != nil {
log.Fatalf("Error when calling SayHello: %s", err)
}
log.Printf("Response from server: %s", response.Body)
}
このプログラムを実行すると、クライアントがサーバーからメッセージを受け取ることができます。
go run client.go
2020/04/30 20:10:09 Response from server: Hello From the Server!
最後に
ここまででGo言語で単純なgRPCクライアントとサーバーを構築する方法について説明してきました。クライアントから着信メッセージを受け入れて、それらのクライアントに応答を返す処理です。
まだこの記事だけではgRPCの一部分しか触れていないので共に勉強していきましょう!
不明点などございましたらお気軽コメントお願いしますmm
参考文献
マイクロサービスバックエンドAPIのためのRESTとgRPC