Slide 1

Slide 1 text

2019/4/18 reveal.js localhost:8000/?print-pdf 1/34 Go+gRPC と protoc プラグイン Go+gRPC と protoc プラグイン

Slide 2

Slide 2 text

2019/4/18 reveal.js localhost:8000/?print-pdf 2/34 自己紹介 自己紹介 yoshd サーバーサイドエンジニア Golang, Python, Ruby, Java

Slide 3

Slide 3 text

2019/4/18 reveal.js localhost:8000/?print-pdf 3/34 話すこと 話すこと gRPC とは Go + gPRC の簡単な例 便利なprotoc プラグイン protoc-gen-doc protoc-gen-grpc-gateway protoc-gen-swagger protoc プラグインの実装方法

Slide 4

Slide 4 text

2019/4/18 reveal.js localhost:8000/?print-pdf 4/34 gPRC とは gPRC とは Google 製のOSS のRPC(Remote Procedure Call) フレームワーク IDL(Interface Definition Language) ・シリアライズフォーマットに Protocol Buffers を使用 インターフェイスを定義した ファイルからサーバー側と クライアント側の様々な言語のソースコードを生成できる 通信プロトコルはHTTP/2 https://grpc.io/

Slide 5

Slide 5 text

2019/4/18 reveal.js localhost:8000/?print-pdf 5/34 Go + gPRC の簡単な例 Go + gPRC の簡単な例

Slide 6

Slide 6 text

2019/4/18 reveal.js localhost:8000/?print-pdf 6/34 やること やること Go でgPRC サーバー・クライアントを実装する 1. と をインストール 2. Protocol Buffers でインターフェイスを定義 3. インターフェイスを定義した ファイルからコード生成 4. 3 で生成されたコードを使ってgRPC サーバーとクライアントを実装 今回の例のソースはこちらにあります https://github.com/yoshd/go-grpc-lt

Slide 7

Slide 7 text

2019/4/18 reveal.js localhost:8000/?print-pdf 7/34 と と をインストール をインストール Protocol Buffers で定義された ファイルを読み取って 対応するクラスや構造体などを生成するコンパイラ Go のコードを生成するために必要な のプラグイン

Slide 8

Slide 8 text

2019/4/18 reveal.js localhost:8000/?print-pdf 8/34 と と をインストール をインストール 公式の Docker image で 公式の Docker image で と と 両方使えます 両方使えます Docker を使わない場合 Docker を使わない場合 protoc protoc-gen-go $ docker pull grpc/go $ wget https://github.com/protocolbuffers/protobuf/releases/download//protoc--

Slide 9

Slide 9 text

2019/4/18 reveal.js localhost:8000/?print-pdf 9/34 Protocol Buffers でインターフェイスを定義 Protocol Buffers でインターフェイスを定義 syntax = "proto3"; // Go 生成 場合 名 option go_package = "pb"; // Sample 定義 service Sample { // Sample Hello gRPC 定義 // 引数 HelloRequest 受 取 HelloResponse 返 rpc Hello (HelloRequest) returns (HelloResponse) {} } // HelloRequest 定義 message HelloRequest { // string型 name 持 string name = 1; } // HelloResponse 定義 message HelloResponse { // string型 message 持 string message = 1; }

Slide 10

Slide 10 text

2019/4/18 reveal.js localhost:8000/?print-pdf 10/34 インターフェイスを定義した インターフェイスを定義した ファイルからコード生成 ファイルからコード生成 が生成される 生成されたコードには下記が含まれる や に対応する構造体 サーバー側で使用する のインターフェイス クライアントが の を呼び出すための実装 など $ protoc sample.proto --go_out=plugins=grpc:pb https://github.com/yoshd/go-grpc-lt/blob/master/pb/sample.pb.go

Slide 11

Slide 11 text

2019/4/18 reveal.js localhost:8000/?print-pdf 11/34 生成されたコードを使って gRPC サーバーを実装 生成されたコードを使って gRPC サーバーを実装 package main import ( "context" "fmt" "net" "github.com/yoshd/go-grpc-lt/pb" "google.golang.org/grpc" ) type server struct{} // 生成 定義 側 interface 実装 func (s *server) Hello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { return &pb.HelloResponse{Message: fmt.Sprintf("Hello %s!", in.GetName())}, nil } func main() { lis, err := net.Listen("tcp", "localhost:13009") if err != nil { panic(err) } s := grpc.NewServer() pb.RegisterSampleServer(s, &server{}) s.Serve(lis) }

Slide 12

Slide 12 text

2019/4/18 reveal.js localhost:8000/?print-pdf 12/34 生成されたコードを使って gRPC クライアントを 生成されたコードを使って gRPC クライアントを 実装 実装 package main import ( "context" "fmt" "github.com/yoshd/go-grpc-lt/pb" "google.golang.org/grpc" ) func main() { conn, err := grpc.Dial("localhost:13009", grpc.WithInsecure()) if err != nil { panic(err) } defer conn.Close() // 生成 使用 簡単 gPRC 呼 c := pb.NewSampleClient(conn) req := pb.HelloRequest{Name: "Yoshd"} res, err := c.Hello(context.Background(), &req) if err != nil { panic(err) } fmt.Println(res.Message) }

Slide 13

Slide 13 text

2019/4/18 reveal.js localhost:8000/?print-pdf 13/34 動かしてみる 動かしてみる サーバー側 クライアント側 超簡単!!! # 生成 使 gRPC 実装 書 実行 $ go run server/main.go # 生成 使 gRPC 実装 書 実行 $ go run client/main.go Hello Yoshd!

Slide 14

Slide 14 text

2019/4/18 reveal.js localhost:8000/?print-pdf 14/34 便利な protoc プラグイン 便利な protoc プラグイン を使えばProtocol Buffers で定義されたスキーマを読み取って、簡単 に様々な言語のコード生成できることはおわかり頂けたと思う でもせっかくIDL で定義してるなら、gRPC のAPI ドキュメント等もスキーマ から生成したいなという気持ちになりますよね? プラグインを使えばできます!

Slide 15

Slide 15 text

2019/4/18 reveal.js localhost:8000/?print-pdf 15/34 gRPC のドキュメントを生成する gRPC のドキュメントを生成する プラグイン プラグイン インストール 呼び出し方は コマンド実行時に下記オプションを指定するだけ $ go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc $ protoc --doc_out= --doc_opt=|,

Slide 16

Slide 16 text

2019/4/18 reveal.js localhost:8000/?print-pdf 16/34 gRPC のドキュメントを生成する gRPC のドキュメントを生成する プラグイン プラグイン 先ほど使った ファイルにドキュメント用のコメントをつけて ( なくてもいい) ドキュメントを生成してみる syntax = "proto3"; option go_package = "pb"; // Sample 挨拶 得意 service Sample { // Hello 返 rpc Hello (HelloRequest) returns (HelloResponse) {} } // message HelloRequest { // 名前 入 string name = 1; } // message HelloResponse { // Hello, 返 string message = 1; }

Slide 17

Slide 17 text

2019/4/18 reveal.js localhost:8000/?print-pdf 17/34 gRPC のドキュメントを生成する gRPC のドキュメントを生成する プラグイン プラグイン ドキュメント生成 $ protoc sample.proto --doc_out=doc --doc_opt=markdown,doc.md

Slide 18

Slide 18 text

2019/4/18 reveal.js localhost:8000/?print-pdf 18/34 gRPC のドキュメントを生成する gRPC のドキュメントを生成する プラグイン プラグイン こんな感じのドキュメントが生成される

Slide 19

Slide 19 text

2019/4/18 reveal.js localhost:8000/?print-pdf 19/34 その他の便利な その他の便利な プラグイン プラグイン protoc-gen-go 冒頭でも触れましたが、実はGo のソースコードを生成するためにはこ のプラグインが必要 protoc-gen-grpc-gateway RESTful JSON API をgRPC に変換するプロキシのソースコードを生成する 簡単にHTTP リクエストをgRPC のリクエストにマッピングできる ファイルにgrpc-gateway 用にREST API のパスやHTTP メソッド等を 記述する必要がある protoc-gen-swagger とセットで使う で生成したRESTful JSON API のための Swagger 用JSON を生成する protoc-gen-govalidators ファイルにバリデーション用の記述をすることで、バリデーシ ョン用コードを生成できる

Slide 20

Slide 20 text

2019/4/18 reveal.js localhost:8000/?print-pdf 20/34 と と を使う を使う 下記のように ファイルを修正 syntax = "proto3"; option go_package = "pb"; import "google/api/annotations.proto"; // Sample 挨拶 得意 service Sample { // Hello 返 rpc Hello (HelloRequest) returns (HelloResponse) { option (google.api.http) = { post: "/v1/sample/hello" body: "*" }; } } // message HelloRequest { // 名前 入 string name = 1; } // message HelloResponse { // Hello, 返 string message = 1; }

Slide 21

Slide 21 text

2019/4/18 reveal.js localhost:8000/?print-pdf 21/34 と と を使う を使う プラグインインストール コード生成 $ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway $ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger $ protoc \ -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ -I. \ --grpc-gateway_out=./pb \ --swagger_out=./swagger \ sample.proto

Slide 22

Slide 22 text

2019/4/18 reveal.js localhost:8000/?print-pdf 22/34 grpc-gateway で REST API -> gPRC のプロキシを立てる grpc-gateway で REST API -> gPRC のプロキシを立てる package main import ( "context" "net/http" "github.com/grpc-ecosystem/grpc-gateway/runtime" gw "github.com/yoshd/go-grpc-lt/pb" "google.golang.org/grpc" ) func main() { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} // 生成 使用 err := gw.RegisterSampleHandlerFromEndpoint(ctx, mux, "localhost:13009", opts) if err != nil { panic(err) } http.ListenAndServe(":8080", mux) }

Slide 23

Slide 23 text

2019/4/18 reveal.js localhost:8000/?print-pdf 23/34 grpc-gateway で REST API -> gPRC のプロキシを立てる grpc-gateway で REST API -> gPRC のプロキシを立てる 実行 grpc-gateway 経由でリクエストしてみる 超簡単!!! $ go run gateway/main.go $ curl localhost:8080/v1/sample/hello -d '{"name":"hoge"}' {"message":"Hello hoge!"}

Slide 24

Slide 24 text

2019/4/18 reveal.js localhost:8000/?print-pdf 24/34 grpc-gateway 用の Swagger grpc-gateway 用の Swagger 先ほど で生成されたJSON は Swagger UI でみることができる

Slide 25

Slide 25 text

2019/4/18 reveal.js localhost:8000/?print-pdf 25/34 protoc プラグインを実装する protoc プラグインを実装する ここまで聞いて、ほとんどのエンジニアは 以下のようなことを思ったのではないでしょうか。 既存の プラグインが便利なのはわかった でもエンジニアなら普通自分でプラグイン作るよね? 大人の事情でExcel にAPI 仕様を書かなければいけないんだけど protobuf スキーマからテスト用のコードも生成できるのでは? 何らかの共通処理をprotobuf スキーマを読み取って自動生成したい など 大丈夫、 protoc プラグインは簡単に作れます 大丈夫、 protoc プラグインは簡単に作れます

Slide 26

Slide 26 text

2019/4/18 reveal.js localhost:8000/?print-pdf 26/34 プラグインの実装 プラグインの実装 のプラグインの実装方法はシンプル 1. から ファイルを読み取った情報を標準入力で受け取りparse 2. 何らかの処理をする 3. 出力したい内容を標準出力する これだけ

Slide 27

Slide 27 text

2019/4/18 reveal.js localhost:8000/?print-pdf 27/34 を実装する を実装する すごーい!${SERVICE} は${METHOD} が得意なフレンズなんだね! と出力するだけのシンプルなプラグインを実装する ソースはこちら https://github.com/yoshd/protoc-gen-friends

Slide 28

Slide 28 text

2019/4/18 reveal.js localhost:8000/?print-pdf 28/34 標準入力を受け取り parse する 標準入力を受け取り parse する package main import ( "io" "io/ioutil" "os" "github.com/golang/protobuf/proto" plugin "github.com/golang/protobuf/protoc-gen-go/plugin" ) // 標準入力 受 // plugin.CodeGeneratorRequest protoc 標準入力 結果 func parse(r io.Reader) (*plugin.CodeGeneratorRequest, error) { buf, err := ioutil.ReadAll(r) if err != nil { return nil, err } var req plugin.CodeGeneratorRequest if err = proto.Unmarshal(buf, &req); err != nil { return nil, err } return &req, nil } ...

Slide 29

Slide 29 text

2019/4/18 reveal.js localhost:8000/?print-pdf 29/34 何らかの処理をする 何らかの処理をする import ( "fmt" "github.com/golang/protobuf/proto" plugin "github.com/golang/protobuf/protoc-gen-go/plugin" ) // 情報 受 取 何 処理 出力 内容 作成 func process(req *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse { files := make(map[string]*descriptor.FileDescriptorProto) for _, f := range req.ProtoFile { files[f.GetName()] = f // 読 取 .proto } var res plugin.CodeGeneratorResponse for _, fname := range req.FileToGenerate { f := files[fname] for _, service := range f.GetService() { content := makeContent(service.GetName(), service.GetMethod()) // 出力 内容 res.File = append(res.File, &plugin.CodeGeneratorResponse_File{ // 出力 内容 入 Name: proto.String("friends.txt"), Content: proto.String(content), }) } } return &res } // 生成 内容 作 func makeContent(serviceName string, methods []*descriptor.MethodDescriptorProto) string { var content string for _, m := range methods { methodName := m.GetName() content += fmt.Sprintf(" %s %s 得意 \n", serviceName, methodName) } return content }

Slide 30

Slide 30 text

2019/4/18 reveal.js localhost:8000/?print-pdf 30/34 出力したい内容を標準出力する 出力したい内容を標準出力する import ( "os" "github.com/golang/protobuf/proto" plugin "github.com/golang/protobuf/protoc-gen-go/plugin" ) // plugin.CodeGeneratorResponse 列 標準出力 func output(res *plugin.CodeGeneratorResponse) error { buf, err := proto.Marshal(res) if err != nil { return err } _, err = os.Stdout.Write(buf) return err } ...

Slide 31

Slide 31 text

2019/4/18 reveal.js localhost:8000/?print-pdf 31/34 main main func main() { req, err := parse(os.Stdin) if err != nil { panic(err) } res := process(req) err = output(res) if err != nil { panic(err) } }

Slide 32

Slide 32 text

2019/4/18 reveal.js localhost:8000/?print-pdf 32/34 の実行 の実行 ビルドしてパスの通った場所に置く プラグイン実行! 生成されたファイルを確認してみる 超簡単!!! $ go build -o protoc-gen-friends plugin.go $ mv protoc-gen-friends $GOPATH/bin/ $ protoc --friends_out=. sample.proto $ cat friends.txt Sample Hello 得意

Slide 33

Slide 33 text

2019/4/18 reveal.js localhost:8000/?print-pdf 33/34 まとめ まとめ gRPC 良いですね IDL+ コード生成できるし色々便利 今回は話してませんが、マイクロサービス間で連携しやすいとか双方 向通信が簡単とか他にも色々メリットがある 便利なprotoc プラグインがたくさんある protoc プラグインの実装は簡単 gRPC のシナリオテスト用コードを生成するプラグインを 簡単に作ってみました gRPC が得意なフレンズを目指していきましょう gRPC が得意なフレンズを目指していきましょう https://github.com/yoshd/protoc-gen-stest

Slide 34

Slide 34 text

2019/4/18 reveal.js localhost:8000/?print-pdf 34/34 おわり おわり