GoでgRPCのサーバーを構築する
go getとかは割愛。こんなファイルを作る。
syntax = "proto3"; option go_package = "proto"; package user; service UserService { rpc ListUser(RequestType) returns (stream User) {} rpc AddUser(User) returns (ResponseType) {} } message ResponseType { } message RequestType { } message User { string id = 1; string email = 2; string name = 3; Status status = 4; } enum Status { ACTIVE = 0; INACTIVE = 1; }
protocコマンドを実行する。引数はprotoファイルのパスだけど、それ以外のオプションがhelp読んでもよくわからんかったorz とりあえず、これでuser.pb.goってライブラリが完成した。
protoc --proto_path=proto --go_out=plugins=grpc:proto-out proto/user.proto
次にserverを実装する。↓を参考にした。
メモがわりのコメント多め。
package main import ( "log" "net" "os" "os/signal" "sync" pb "./proto-out" // ここはgithub上のパス(例:"google.golang.org/grpc/examples/helloworld/helloworld")にしてるっぽいけど一旦こうする "golang.org/x/net/context" "google.golang.org/grpc" ) const ( port = ":50051" // ポート番号 ) // Serverというインターフェースを定義する type Server struct { users []*pb.User m sync.Mutex // ここでMutexを持たせなくてもいいかもしれない(参考:https://qiita.com/h3_poteto/items/3a39c41743b4fd87c134) } // ServerインターフェースにListUserメソッドとAddUserメソッドを追加する。 // これでGoのダッグタイピングによって、UserServiceServerを実装したことと同じになる func (cs *Server) ListUser(p *pb.RequestType, stream pb.UserService_ListUserServer) error { cs.m.Lock() // ロックする defer cs.m.Unlock() // deferはこの関数終了時に行う処理を定義する。finalyみたいなもん for _, p := range cs.users { if err := stream.Send(p); err != nil { return err } } return nil } func (cs *Server) AddUser(c context.Context, p *pb.User) (*pb.ResponseType, error) { cs.m.Lock() defer cs.m.Unlock() cs.users = append(cs.users, p) return new(pb.ResponseType), nil } func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("faild to listen: %v", err) } server := grpc.NewServer() // new(T)は、型Tの新しいアイテム用にゼロ化した領域を割り当て、そのアドレスである*T型の値を返す。 // `ゼロ化した領域`はすなわちinitされた領域だと思われる。 // 参考:http://golang.jp/effective_go#allocation_new pb.RegisterUserServiceServer(server, new(Server)) go func() { log.Printf("start grpc Server port: %s", port) server.Serve(lis) }() // os.Signal型のチャネルを定義して、 // <-quitのところでブロッキングして、 // quitメッセージが来たらサーバーを停止させている。 quit := make(chan os.Signal) // 多分、OSからのInterruptが来たら、quitキューに入れる、ってことを定義している signal.Notify(quit, os.Interrupt) <-quit log.Println("stopping grpc Server...") server.GracefulStop() }
サーバーを起動してみる。
go run user_server.go 2018/05/13 23:19:52 start grpc Server port: :50051 ^C2018/05/13 23:19:57 stopping grpc Server...
次にクライアントを作る。↓参考にした
gRPC(Go) で API を実装する (フェンリル | デベロッパーズブログ)
package main import ( "log" "time" "golang.org/x/net/context" "google.golang.org/grpc" pb "./proto-out" "io" "fmt" ) const ( address = "localhost:50051" ) func main() { conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatalln("did not connect: %v", err) } defer conn.Close() c := pb.NewUserServiceClient(conn) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() user_1 := pb.User{ Id: "doilux1", Email: "doilux1@example.com", Name: "hoge fuga" } user_2 := pb.User{ Id: "doilux2", Email: "doilux2@example.com", Name: "hoge fuga"} user_3 := pb.User{ Id: "doilux3", Email: "doilux3@example.com", Name: "hoge fuga"} c.AddUser(ctx, &user_1) c.AddUser(ctx, &user_2) c.AddUser(ctx, &user_3) stream, err := c.ListUser(ctx, &pb.RequestType{}) if err != nil { log.Fatalln("could not get user: %v", err) } for { msg, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatalln("Receive:", err) } fmt.Println(msg) } }
実行してみる。サーバーを起動した状態で以下実行する。
go run user_client.go id:"doilux1" email:"doilux1@example.com" name:"hoge fuga" id:"doilux2" email:"doilux2@example.com" name:"hoge fuga" id:"doilux3" email:"doilux3@example.com" name:"hoge fuga"
一応、できたことはできた。
わからなかったこと
ロジックどこに書く?
例えば、「すでに使われているIDは登録できない」とか、そんなルールがあるときにどこに実装するんだろう?やっぱりここになるんだろうか(実際にはどこかに委譲するとおもうけど)
func (cs *Server) AddUser(c context.Context, p *pb.User) (*pb.ResponseType, error) { cs.m.Lock() defer cs.m.Unlock() cs.users = append(cs.users, p) return new(pb.ResponseType), nil }
エラーの時どうする?
上記に関連して、ビジネスエラーだったときに一般的にどんなレスポンスを返してるんだろうか。
ということで今日はこれまで
追記
これが参考になりそう
GitHub - harlow/go-micro-services: HTTP up front, Protobufs in the rear