gRPCサーバを含むGoプロジェクトをBazelでビルドする
前回までのエントリに引き続きBazelのビルドをまとめていく。
今回のエントリではgRPCサーバを含むGoプロジェクトをBazelでビルドする方法をまとめる。
Protocol BufferとgRPCサーバのInterfaceの生成をBazelで行う
これまではprotocをつかって.protoファイルからProtocol BufferとgRPCサーバのInterfaceの生成を行っていたがBazelのruleを使えば簡単にビルドタスクに追加することができる。
利用するruleはrules_proto
。
このruleに辿り着く前にpubref/rules_protobufを見つけたがpubrefのほうは周辺のruleのアップデートに追いついておらずstackbのほうがメンテナンスが活発である。
WORKSPACEにruleの追加と.protoファイルの追加
WORKSPACEにruleを追加する。
# WORKSPACE http_archive( name = "build_stack_rules_proto", urls = ["https://github.com/stackb/rules_proto/archive/1d6550fc2e62.tar.gz"], sha256 = "113e6792f5b20679285c86d91c163cc8c4d2b4d24d7a087ae4f233b5d9311012", strip_prefix = "rules_proto-1d6550fc2e625d47dc4faadac92d7cb20e3ba5c5", )
シンプルなecho.protoをproto/echo
ディレクトリ以下に追加する。
# proto/echo/echo.proto syntax = "proto3"; package echo; service Echo { rpc Echo (EchoInbound) returns (EchoOutbound); } message EchoInbound { string message = 1; } message EchoOutbound { string message = 1; }
この状態でgazelle
を実行するとproto/echo
以下にBUILD.bazel
が追加されている。
(bazel-multiprojects) $ bazel run //:gazelle
# proto/echo/BUILD.bazel load("@io_bazel_rules_go//go:def.bzl", "go_library") load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") proto_library( name = "echo_proto", srcs = ["echo.proto"], visibility = ["//visibility:public"], ) go_proto_library( name = "echo_go_proto", compilers = ["@io_bazel_rules_go//proto:go_grpc"], importpath = "github.com/soushin/bazel-multiprojects/proto/echo", proto = ":echo_proto", visibility = ["//visibility:public"], ) go_library( name = "go_default_library", embed = [":echo_go_proto"], importpath = "github.com/soushin/bazel-multiprojects/proto/echo", visibility = ["//visibility:public"], )
ここで生成されたgo_libraryのgo_default_library
を後でgRPCサーバ側のソースに依存させる。
go_grpc_compileをつかってProtocol Bufferを生成するタスクを追加する
go_default_library
のビルドタスクのみだとecho.pb.go
ファイルが生成されない。ここでstackb/rules_proto
のruleを追加する。proto/echo/BUILD.bazel
に以下を追加する。
load("@build_stack_rules_proto//go:go_grpc_compile.bzl", "go_grpc_compile") go_grpc_compile( name = "proto_buf", deps = [":echo_proto"], )
このproto_buf
のタスクを実行するとbazel-genfiles/proto/echo/proto_buf/proto/echo/
以下にecho.pb.go
ファイルが生成される。
(bazel-multiprojects) $ bazel build //proto/echo:proto_buf INFO: Analysed target //proto/echo:proto_buf (0 packages loaded). INFO: Found 1 target... Target //proto/echo:proto_buf up-to-date: bazel-genfiles/proto/echo/proto_buf/proto/echo/echo.pb.go INFO: Elapsed time: 0.261s, Critical Path: 0.08s INFO: 1 process: 1 darwin-sandbox. INFO: Build completed successfully, 2 total actions
生成されたecho.pb.go
ファイルをプロジェクトの/proto/echo
に配置することでgRPCサーバ側のコードにimportすることができる。
gRPCサーバを実装する
gRPCサーバのコードではgoogle.golang.org/grpc
のライブラリを利用するのでWORKSPACEに外部ライブラリの依存を追加しておく。
go_repository( name = "org_golang_google_grpc", importpath = "github.com/grpc/grpc-go", tag = "v1.16.0", )
main.goのコードは次のような差分になった。シンプルにechoServerを実装しているコードである。
# pkg/public_go/main.go diff --git a/pkg/public_go/main.go b/pkg/public_go/main.go index 73c925b..34bddcc 100644 --- a/pkg/public_go/main.go +++ b/pkg/public_go/main.go @@ -1,17 +1,29 @@ package main import ( + "context" "flag" "fmt" - "context" "log" + "net" "net/http" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + "github.com/soushin/bazel-multiprojects/pkg/common_go/util" + "github.com/soushin/bazel-multiprojects/proto/echo" ) var msg string +type server struct{} + +func (s *server) Echo(ctx context.Context, in *echo.EchoInbound) (*echo.EchoOutbound, error) { + return &echo.EchoOutbound{ + Message: in.Message}, nil +} + func main() { greet := flag.String("greet", "Hello", "greet message") flag.Parse() @@ -21,6 +33,22 @@ func main() { } msg = greetUsecase.Msg + // serve gRPC server + lis, err := net.Listen("tcp", ":50051") + defer lis.Close() + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + grpcServer := grpc.NewServer() + server := &server{} + echo.RegisterEchoServer(grpcServer, server) + reflection.Register(grpcServer) + go func() { + if err := grpcServer.Serve(lis); err != nil { + log.Fatalf("failed to serve grpc server") + } + }() + http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) }
上記のコードの状態でgazelle
を実行するとpkg/public_go/BUILD.bazel
に依存が追加される。
diff --git a/pkg/public_go/BUILD.bazel b/pkg/public_go/BUILD.bazel index 0c4c20d..24250db 100644 --- a/pkg/public_go/BUILD.bazel +++ b/pkg/public_go/BUILD.bazel @@ -11,6 +11,9 @@ go_library( deps = [ "//pkg/common_go/util:go_default_library", "//pkg/public_go/usecase:go_default_library", + "//proto/echo:go_default_library", + "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_grpc//reflection:go_default_library", ], )
gazelleがWORKSPACEの外部ライブラリ依存の定義とmain.goのコードを解析して依存関係をBUILD定義に反映してくれている。
ここまでくればgRPCサーバを起動することができる。
gRPCサーバの起動
pkg/public_go/BUILD.bazel
に定義されているgo_binaryのタスクのrunコマンドを実行してサーバを起動させる。
$ (bazel-multiprojects) $ bazel run //pkg/public_go:public_go
gRPCのデバッグに最適なevansで起動したgRPCサーバにリクエストを送り確認する。
evans --port 50051 proto/echo/echo.proto ______ | ____| | |__ __ __ __ _ _ __ ___ | __| \ \ / / / _. | | '_ \ / __| | |____ \ V / | (_| | | | | | \__ \ |______| \_/ \__,_| |_| |_| |___/ more expressive universal gRPC client 127.0.0.1:50051> show package +---------+ | PACKAGE | +---------+ | echo | +---------+ 127.0.0.1:50051> package echo echo@127.0.0.1:50051> show service +---------+------+-------------+--------------+ | SERVICE | RPC | REQUESTTYPE | RESPONSETYPE | +---------+------+-------------+--------------+ | Echo | Echo | EchoInbound | EchoOutbound | +---------+------+-------------+--------------+ echo@127.0.0.1:50051> service Echo echo.Echo@127.0.0.1:50051> call Echo message (TYPE_STRING) => Awesome Bazel! { "message": "Awesome Bazel!" }
EchoSeverが正常に起動していることを確認できた。
まとめ
- gRPCサーバを含むGoプロジェクトをBazelでビルドする方法をまとめた
- Protocol Bufferを生成するruleもありGoプロジェクト周辺のビルドユースケースが整っている