--- title: Building gRPC services with bazel and rules_protobuf date: 2016-10-13 author: name: Paul Cody Johnston link: https://pubref.org position: PubRef.org thumbnail: https://avatars3.githubusercontent.com/u/10408150?v=3&s=200 --- [gRPC](/) makes it easier to build high-performance microservices by providing generated service entrypoints in a variety of different languages. [Bazel](https://bazel.io) complements these efforts with a capable and fast polyglot build environment. [rules_protobuf](https://github.com/pubref/rules_protobuf) extends bazel and makes it easier develop gRPC services. It does this by: 1. Building `protoc` (the protocol buffer compiler) and all the necessary `protoc-gen-*` plugins. 1. Building the protobuf and gRPC libraries required for gRPC-related code to compile. 1. Abstracting away `protoc` plugin invocation (you don't have to necessarily learn or remember how to call `protoc`). 1. Regenerating and recompiling outputs when protobuf source files change. In this post I'll provide background about how bazel works ([Part 1](#about-bazel)) and how to get started building gRPC services with rules_protobuf ([Part 2](#building)). If you're already a bazel aficionado, you can skip directly to Part 2. To best follow along, [install bazel](https://www.bazel.io/versions/master/docs/install.html) and clone the rules_protobuf repository: ```sh git clone https://github.com/pubref/rules_protobuf cd rules_protobuf ~/rules_protobuf$ ``` Great. Let's get started! # 1: About Bazel {#about-bazel} [Bazel](https://www.bazel.io/) is Google's open-source version of their internal build tool called "Blaze". Blaze originated from the challenges of managing a large monorepo with code written in a variety of languages. Blaze was the inspiration for other capable and fast build tools including [Pants](https://www.pantsbuild.org/) and [Buck](https://buckbuild.com/). Bazel is conceptually simple but there are some core concepts & terminology to understand: 1. **Bazel command**: a function that does some type of work when called from the command line. Common ones include `bazel build` (compile a libary), `bazel run` (run a binary executable), `bazel test` (execute tests), and `bazel query` (tell me something about the build dependency graph). See all with `bazel help`. 1. **Build phases**: the three stages (loading, analysis, and execution) that bazel goes through when calling a bazel command. 2. **The WORKSPACE file**: a required file that defines the project root. It is primarily used to declare external dependencies (external workspaces). 3. **BUILD files**: the presence of a `BUILD` file in a directory defines it as a *package*. `BUILD` files contain *rules* that define *targets* which can be selected using the *target pattern syntax*. Rules are written in a python-like language called [*skylark*](https://bazel.io/versions/master/docs/skylark/index.html). Syklark has stronger deterministic guarantees than python but is intentionally minimal, excluding language features such as recursion, classes, and lambdas. ## 1.1: Package Structure To illustrate these concepts, let's look at the package structure of the [rules_protobuf examples subdirectory](https://github.com/pubref/rules_protobuf/tree/master/examples). Let's look at the file tree, showing only those folder having a `BUILD` file: ```diff tree -P 'BUILD|WORKSPACE' -I 'third_party|bzl' examples/ . ├── BUILD ├── WORKSPACE └── examples    ├── helloworld    │   ├── cpp    │   │   └── BUILD    │   ├── go    │   │   ├── client    │   │   │   └── BUILD    │   │   ├── greeter_test    │   │   │   └── BUILD    │   │   └── server    │   │   └── BUILD    │   ├── grpc_gateway    │   │   └── BUILD    │   ├── java    │   │   └── org    │   │   └── pubref    │   │   └── rules_protobuf    │   │   └── examples    │   │   └── helloworld    │   │   ├── client    │   │   │   └── BUILD    │   │   └── server    │   │   └── BUILD    │   └── proto    │      └── BUILD    └── proto    └── BUILD ``` ## 1.2: Targets To get a list of targets within the `examples/` folder, use a query. This says *"Ok bazel, show me all the callable targets in all packages within the examples folder, and say what kind of thing it is in addition to its path label"*: ```bash ~/rules_protobuf$ bazel query //examples/... --output label_kind | sort | column -t cc_binary rule //examples/helloworld/cpp:client cc_binary rule //examples/helloworld/cpp:server cc_library rule //examples/helloworld/cpp:clientlib cc_library rule //examples/helloworld/proto:cpp cc_library rule //examples/proto:cpp cc_proto_compile rule //examples/helloworld/proto:cpp.pb cc_proto_compile rule //examples/proto:cpp.pb cc_test rule //examples/helloworld/cpp:test filegroup rule //examples/helloworld/proto:protos filegroup rule //examples/proto:protos go_binary rule //examples/helloworld/go/client:client go_binary rule //examples/helloworld/go/server:server go_library rule //examples/helloworld/go/server:greeter go_library rule //examples/helloworld/grpc_gateway:gateway go_library rule //examples/helloworld/proto:go go_library rule //examples/proto:go_default_library go_proto_compile rule //examples/helloworld/proto:go.pb go_proto_compile rule //examples/proto:go_default_library.pb go_test rule //examples/helloworld/go/greeter_test:greeter_test go_test rule //examples/helloworld/grpc_gateway:greeter_test grpc_gateway_proto_compile rule //examples/helloworld/grpc_gateway:gateway.pb java_binary rule //examples/helloworld/java/org/pubref/rules_protobuf/examples/helloworld/client:netty java_binary rule //examples/helloworld/java/org/pubref/rules_protobuf/examples/helloworld/server:netty java_library rule //examples/helloworld/java/org/pubref/rules_protobuf/examples/helloworld/client:client java_library rule //examples/helloworld/java/org/pubref/rules_protobuf/examples/helloworld/server:server java_library rule //examples/helloworld/proto:java java_library rule //examples/proto:java java_proto_compile rule //examples/helloworld/proto:java.pb java_proto_compile rule //examples/proto:java.pb js_proto_compile rule //examples/helloworld/proto:js js_proto_compile rule //examples/proto:js py_proto_compile rule //examples/helloworld/proto:py.pb ruby_proto_compile rule //examples/proto:rb.pb ``` We're not limited to targets in our own workspace. As it turns out, the [Google Protobuf repo](https://github.com/google/protobuf) is named as an external repository (more on this later) and we can also address targets in that workspace in the same way. Here's a partial list: ```bash ~/rules_protobuf$ bazel query @com_github_google_protobuf//... --output label_kind | sort | column -t cc_binary rule @com_github_google_protobuf//:protoc cc_library rule @com_github_google_protobuf//:protobuf cc_library rule @com_github_google_protobuf//:protobuf_lite cc_library rule @com_github_google_protobuf//:protoc_lib cc_library rule @com_github_google_protobuf//util/python:python_headers filegroup rule @com_github_google_protobuf//:well_known_protos java_library rule @com_github_google_protobuf//:protobuf_java objc_library rule @com_github_google_protobuf//:protobuf_objc py_library rule @com_github_google_protobuf//:protobuf_python ... ``` This is possible because the protobuf team provides a [BUILD file](https://github.com/google/protobuf/blob/master/BUILD) at the root of their repository. Thanks Protobuf team! Later we'll learn how to "inject" our own BUILD files into repositories that don't already have one. Inspecting the list above, we see a `cc_binary` rule named `protoc`. If we `bazel run` that target, bazel will clone the protobuf repo, build all the dependent libraries, build a pristine executable binary from source, and call it (pass command line arguments to binary rules after the double-dash): ```bash ~/rules_protobuf$ bazel run @com_github_google_protobuf//:protoc -- --help Usage: /private/var/tmp/_bazel_pcj/63330772b4917b139280caef8bb81867/execroot/rules_protobuf/bazel-out/local-fastbuild/bin/external/com_github_google_protobuf/protoc [OPTION] PROTO_FILES Parse PROTO_FILES and generate output based on the options given: -IPATH, --proto_path=PATH Specify the directory in which to search for imports. May be specified multiple times; directories will be searched in order. If not given, the current working directory is used. --version Show version info and exit. -h, --help Show this text and exit. ... ``` As we'll see in a moment, *we name the protobuf external dependency with a specific commit ID so there's no ambiguity about which protoc version we're using*. In this way you can vendor in tools with your project with reliable, repeable, secure precision without bloating your repository by checking in binaries, resorting to git submodules, or similar hacks. Very clean! > Note: the gRPC repository also has a BUILD file: `$ bazel query > @com_github_grpc_grpc//... --output label_kind` ## 1.3: Target Pattern Syntax With those examples under our belt, let's examine the target syntax a bit more. When I first started working with bazel I found the target-pattern syntax somewhat intimidating. It's actually not too bad. Here's a closer look: ![](/img/target-pattern-syntax.png) * The `@` (at-sign) selects an external workspace. These are established by [workspace rules](https://bazel.io/docs/be/workspace.html#workspace-rules) that bind a name to something fetched over the network (or your filesystem). * The `//` (double-slash) selects the workspace root. * The `:` (colon) selects a target (rule or file) within a *package*. Recall that a package is established by the presence of a `BUILD` file in a subfolder of the workspace. * The `/` (single-slash) selects a folder within a workspace or package. > A common source of confusion is that the mere presence of a > BUILD file defines that filesystem subtree as a package and > therefore one must always account for that. For example, if there > exists a file `qux.js` in `foo/bar/baz/` and there exists a BUILD > file in `baz/` also, the file is selected with `foo/bar/baz:qux.js` > and not `foo/bar/baz/quz.js` *Common shortcut*: if there exists a rule having the same name as the package, this is the implied target and can be omitted. For example, there is a `:jar` target in the `//jar` package in the external workspace `com_google_guava_guava`, so the following are eqivalent: ```python deps = ["@com_google_guava_guava//jar:jar"] deps = ["@com_google_guava_guava//jar"] ``` ## 1.4: External Dependencies: Workspace Rules Many large organizations check-in in all the required tools, compilers, linkers, etc to guarantee correct, repeatable builds. With external workspaces, one can effectively accomplish the same thing without bloating your repository. > Note: the bazel convention is to use a fully-namespaced identifier > for external dependency names (replacing special chars with > underscore). For example, the remote repository URL is > https://github.com/google/protobuf.git. This is simplified to the > workspace identifier com_github_google_protobuf. Similarly, by > convention the jar artifact `io.grpc:grpc-netty:jar:1.0.0-pre1` > becomes `io_grpc_grpc_netty`. ### 1.4.1: Workspace Rules that require a pre-existing WORKSPACE These rules assume that the remote resource or URL contains a WORKSPACE file at the top of the file tree and BUILD files that define rule targets. These are referred to as *bazel repositories*. * [git_repository](https://bazel.io/docs/be/workspace.html#git_repository): external bazel dependency from a git repository. The rule requires `commit` (or `tag`). * [http_archive](https://bazel.io/docs/be/workspace.html#http_archive): an external zip or tar.gz dependency from a URL. It is highly recommended to name a sha265 for security. > Note: although you don't interact directly with the bazel > execution_root, you can peek at what these external dependencies > look like when unpacked at `$(bazel info > execution_root)/external/WORKSPACE_NAME`. ### 1.4.2: Workspace Rules that autogenerate a WORKSPACE file for you The implementation of these repository rules contain logic to autogenerate a WORKSPACE file and BUILD file(s) to make resources available. As always, it is recommended to provide a known sha265 for security to prevent a malicious agent from slipping in tainted code via a compromised network. * [http_jar](https://bazel.io/docs/be/workspace.html#http_jar): external jar from a URL. The jar file is available as a `java_library` dependency as `@WORKSPACE_NAME//jar`. * [maven_jar](https://bazel.io/docs/be/workspace.html#maven_jar): external jar from a URL. The jar file is available as a `java_library` dependency as `@WORKSPACE_NAME//jar`. * [http_file](https://bazel.io/docs/be/workspace.html#http_file): external file from a URL. The resource is available as a `filegroup` via `@WORKSPACE_NAME//file`. For example, we can peek at the generated BUILD file for the `maven_jar` guava dependency via: ```bash ~/rules_protobuf$ cat $(bazel info execution_root)/external/com_google_guava_guava/jar/BUILD ``` ```python # DO NOT EDIT: automatically generated BUILD file for maven_jar rule com_google_guava_guava java_import( name = 'jar', jars = ['guava-19.0.jar'], visibility = ['//visibility:public'] ) filegroup( name = 'file', srcs = ['guava-19.0.jar'], visibility = ['//visibility:public'] ) ``` > Note: the external workspace directory won't exist until you > actually need it, so you'll have to have built a target that > requires it, such as `bazel build > examples/helloworld/java/org/pubref/rules_protobuf/examples/helloworld/client` ### 1.4.3: Workspace Rules that accept a BUILD file as an argument If a repository has no BUILD file(s), you can put one into its filesystem root to adapt the external resource into bazel's worldview and make those resources available to your project. For example, consider [Mark Adler's zlib library](https://github.com/madler/zlib). To start, let's learn what depends on this code. This query says "*Ok bazel, for all targets in examples, find all dependencies (a transitive closure set), then tell me which ones depend on the zlib target in the root package of the external workspace com_github_madler_zlib.*" Bazel reports this reverse dependency set. We request the output in graphviz format and pipe this to dot to generate the figure: ```bash ~/rules_protobuf$ bazel query "rdeps(deps(//examples/...), @com_github_madler_zlib//:zlib)" \ --output graph | dot -Tpng -O ``` ![](/img/zlib-deps.png) So we can see that all grpc-related C code ultimately depends on this library. But, there is no BUILD file in Mark's repo... where did it come from? By using the variant workspace rule `new_git_repository`, we can provide our [own BUILD file](https://github.com/pubref/rules_protobuf/blob/master/protobuf/build_file/com_github_madler_zlib.BUILD) (which defines the `cc_library` target) as follows: ```python new_git_repository( name = "com_github_madler_zlib", remote = "https://github.com/madler/zlib", tag: "v1.2.8", build_file: "//bzl:build_file/com_github_madler_zlib.BUILD", ) ``` This `new_*` family of workspace rules keeps your repository lean and allows you to vendor in pretty much any type of network-available resource. Awesome! * [new_git_repository](https://bazel.io/docs/be/workspace.html#new_git_repository) * [new_local_repository](https://bazel.io/docs/be/workspace.html#new_local_repository) * [new_http_archive](https://bazel.io/docs/be/workspace.html#new_http_archive) > You can also > [write your own repository rules](https://bazel.io/docs/skylark/repository_rules.html) > that have custom logic to pull resources from the net and bind it > into bazel's view of the universe. ## 1.5: Bazel Summary When presented with a command and a target-pattern, bazel goes through the following three phases: 1. Loading: Read the WORKSPACE and required BUILD files. Generate a dependency graph. 2. Analysis: for all nodes in the graph, which nodes are actually required for this build? Do we have all the necessary resources available? 3. Execution: execute each required node in the dependency graph and generate outputs. Hopefully you now have enough conceptual knowledge of bazel to be productive. ## 1.6: rules_protobuf [rules_protobuf](https://github.com/pubref/rules_protobuf) is an extension to bazel that takes care of: 1. Building the protocol buffer compiler `protoc`, 2. Downloading and/or building all the necessary protoc-gen plugins. 2. Downloading and/or building all the necessary gRPC-related support libraries. 3. Invoking protoc for you (on demand), smoothing out the idiosyncracies of different protoc plugins. It works by passing one or more `proto_language` specifications to the `proto_compile` rule. A `proto_language` rule contains the metadata about how to invoke the plugin and the predicted file outputs, while the `proto_compile` rule interprets a `proto_language` spec and builds the appropriate command-line arguments to `protoc`. For example, here's how we can generate outputs for multiple languages simultaneously: ```python proto_compile( name = "pluriproto", protos = [":protos"], langs = [ "//cpp", "//csharp", "//closure", "//ruby", "//java", "//java:nano", "//python", "//objc", "//node", ], verbose = 1, with_grpc = True, ) ``` ```sh bazel build :pluriproto # ************************************************************ cd $(bazel info execution_root) && bazel-out/host/bin/external/com_github_google_protobuf/protoc \ --plugin=protoc-gen-grpc-java=bazel-out/host/genfiles/third_party/protoc_gen_grpc_java/protoc_gen_grpc_java \ --plugin=protoc-gen-grpc=bazel-out/host/bin/external/com_github_grpc_grpc/grpc_cpp_plugin \ --plugin=protoc-gen-grpc-nano=bazel-out/host/genfiles/third_party/protoc_gen_grpc_java/protoc_gen_grpc_java \ --plugin=protoc-gen-grpc-csharp=bazel-out/host/genfiles/external/nuget_grpc_tools/protoc-gen-grpc-csharp \ --plugin=protoc-gen-go=bazel-out/host/bin/external/com_github_golang_protobuf/protoc_gen_go \ --descriptor_set_out=bazel-genfiles/examples/proto/pluriproto.descriptor_set \ --ruby_out=bazel-genfiles \ --python_out=bazel-genfiles \ --cpp_out=bazel-genfiles \ --grpc_out=bazel-genfiles \ --objc_out=bazel-genfiles \ --csharp_out=bazel-genfiles/examples/proto \ --java_out=bazel-genfiles/examples/proto/pluriproto_java.jar \ --javanano_out=ignore_services=true:bazel-genfiles/examples/proto/pluriproto_nano.jar \ --js_out=import_style=closure,error_on_name_conflict,binary,library=examples/proto/pluriproto:bazel-genfiles \ --js_out=import_style=commonjs,error_on_name_conflict,binary:bazel-genfiles \ --go_out=plugins=grpc,Mexamples/proto/common.proto=github.com/pubref/rules_protobuf/examples/proto/pluriproto:bazel-genfiles \ --grpc-java_out=bazel-genfiles/examples/proto/pluriproto_java.jar \ --grpc-nano_out=ignore_services=true:bazel-genfiles/examples/proto/pluriproto_nano.jar \ --grpc-csharp_out=bazel-genfiles/examples/proto \ --proto_path=. \ examples/proto/common.proto # ************************************************************ examples/proto/common_pb.rb examples/proto/pluriproto_java.jar examples/proto/pluriproto_nano.jar examples/proto/common_pb2.py examples/proto/common.pb.h examples/proto/common.pb.cc examples/proto/common.grpc.pb.h examples/proto/common.grpc.pb.cc examples/proto/Common.pbobjc.h examples/proto/Common.pbobjc.m examples/proto/pluriproto.js examples/proto/Common.cs examples/proto/CommonGrpc.cs examples/proto/common.pb.go examples/proto/common_pb.js examples/proto/pluriproto.descriptor_set ``` The various `*_proto_library` rules (that we'll be using below) internally invoke this `proto_compile` rule, then consume the generated outputs and compile them with the requisite libraries into `.class`, `.so`, `.a` (or whatever) objects. So let's *make something* already! We'll use bazel and rules_protobuf to build a gRPC application. # 2: Building a gRPC service with rules_protobuf {#building} The application will involve communication between two different gRPC services: ## 2.1: Services 1. **The Greeter service**: This is the familiar "Hello World" starter example that accepts a request with a `user` argument and replies back with the string `Hello {user}`. 1. **The GreeterTimer service**: This gRPC service will repeatedly call a Greeter service in batches and report back aggregate batch times (in milliseconds). In this way we can compare some average rpc times for the different Greeter service implementations. > This is an informal benchmark intended only for demonstration of > building gRPC applications. For more formal performance testing, > consult the > [gRPC performance dashboard](https://performance-dot-grpc-testing.appspot.com/explore?dashboard=5760820306771968). ## 2.2: Compiled Programs For the demo, we'll use 6 different compiled programs written in 4 languages: * A `GreeterTimer` client (go). This command-line interface requires the `greetertimer.proto` service definition defined locally in the `//proto:greetertimer.proto` file. * A `GreeterTimer` server (java). This netty-based server requires both the `//proto/greetertimer.proto` file and the proto definition defined externally in `@org_pubref_rules_protobuf//examples/helloworld/proto:helloworld.proto`. * Four `Greeter` server implementations (C++, java, go, and C#). rules_protobuf already provides these example implementations, so we'll just use them directly. ## 2.3: Protobuf Definitions GreeterTimer accepts a unary `TimerRequest` and streams back a sequence of `BatchReponse` until all messages have been processed, at which point the remote procedure call is complete. ```c service GreeterTimer { // Unary request followed by multiple streamed responses. // Response granularity will be set by the request batch size. rpc timeHello(TimerRequest) returns (stream BatchResponse); } ``` `TimerRequest` includes metadata about where to contact the Greeter service, how many total RPC calls to make, and how frequent to stream back a BatchResponse (configured via the batch size). ```c message TimerRequest { // the host where the grpc server is running string host = 1; // The port of the grpc server int32 port = 2; // The total number of hellos int32 total = 3; // The number of hellos before sending a BatchResponse. int32 batchSize = 4; } ``` `BatchResponse` reports the number of calls made in the batch, how long the batch run took, and the number of remaining calls. ```c message BatchResponse { // The number of checks that are remaining, calculated relative to // totalChecks in the request. int32 remaining = 1; // The number of checks actually performed in this batch. int32 batchCount = 2; // The number of checks that failed. int32 errCount = 3; // The total time spent, expressed as a number of milliseconds per // request batch size (total time spent performing batchSize number // of health checks). int64 batchTimeMillis = 4; } ``` The non-streaming `Greeter` service takes a unary `HelloRequest` and responds with a single `HelloReply`: ```c service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; common.Config config = 2; } message HelloReply { string message = 1; } ``` > The `common.Config` message type is not particularly functional here > but serves to demonstrate the use of imports. rules_protobuf can > help with more complex setups having multiple proto → proto > dependencies. ## 2.4: Build the grpc_greetertimer example application. This demo application can be cloned at [https://github.com/pubref/grpc_greetertimer](https://github.com/pubref/grpc_greetertimer). ### 2.4.1: Create the Project Layout Here's the directory layout and relevant BUILD files we'll be using: ```sh mkdir grpc_greetertimer && cd grpc_greetertimer ~/grpc_greetertimer$ mkdir -p proto/ go/ java/org/pubref/grpc/greetertimer/ ~/grpc_greetertimer$ touch WORKSPACE ~/grpc_greetertimer$ touch proto/BUILD ~/grpc_greetertimer$ touch proto/greetertimer.proto ~/grpc_greetertimer$ touch go/BUILD ~/grpc_greetertimer$ touch go/main.go ~/grpc_greetertimer$ touch java/org/pubref/grpc/greetertimer/BUILD ~/grpc_greetertimer$ touch java/org/pubref/grpc/greetertimer/GreeterTimerServer.java ``` ### 2.4.2: The WORKSPACE We'll begin by creating the [WORKSPACE](https://github.com/pubref/grpc_greetertimer) file with a reference to the rules_protobuf repository. We load the main entrypoint skylark file [rules.bzl](https://github.com/pubref/rules_protobuf/blob/master/protobuf/rules.bzl) in the `//bzl` package and call its `protobuf_repositories` function with the languages to we want to use (in this case `java` and `go`). We also load [rules_go](https://github.com/bazelbuild/rules_go) for go compile support (not shown). ```python # File //:WORKSPACE workspace(name = "org_pubref_grpc_greetertimer") git_repository( name = "org_pubref_rules_protobuf", remote = "https://github.com/pubref/rules_protobuf.git", tag = "v0.6.0", ) # Load language-specific dependencies load("@org_pubref_rules_protobuf//java:rules.bzl", "java_proto_repositories") java_proto_repositories() load("@org_pubref_rules_protobuf//go:rules.bzl", "go_proto_repositories") go_proto_repositories() ``` > Refer to the > [repositories.bzl file](https://github.com/pubref/rules_protobuf/protobuf/internal/repositories.bzl), > if you are interested in inspecting the dependencies. Bazel won't actually *fetch* something unless we actually need it by some other rule later, so let's go ahead and write some code. We'll store our protocol buffer sources in `//proto`, our java sources in `//java`, and go source in `//go`. > Note: go development within a bazel workspace is a little different > than vanilla go. In particular, one does not have to adhere to a > typical `GOCODE` layout having a `src/`, `pkg/`, `bin/` > subdirectories. ### 2.4.3: The GreeterTimer Server The [Java server's](https://github.com/pubref/grpc_greetertimer/blob/master/java/org/pubref/grpc/greetertimer/GreeterTimerServer.java) main job is to accept requests and then connect to the requested Greeter service as a client. The implementation counts down the number of remaining messages and does a blocking `sayHello(request)` for each one. If the batchSize limit is met, the `observer.onNext(response)` message is invoked, streaming back a response to the client. ```java /* File //java/org/pubref/grpc/greetertimer:GreeterTimerServer.java */ while (remaining-- > 0) { if (batchCount++ == batchSize) { BatchResponse response = BatchResponse.newBuilder() .setRemaining(remaining) .setBatchCount(batchCount) .setBatchTimeMillis(batchTime) .setErrCount(errCount) .build(); observer.onNext(response); } blockingStub.sayHello(HelloRequest.newBuilder() .setName("#" + remaining) .build()); } } ``` ### 2.4.4: The GreeterTimer Client The [Go client](https://github.com/pubref/grpc_greetertimer/blob/master/go/main.go) prepares a `TimerRequest` and gets back a stream interface from the `client.TimeHello` method. We call its `Recv()` method until EOF, at which point the call is complete. A summary of each BatchResponse is simply printed out to the terminal. ```go // File: //go:main.go func submit(client greeterTimer.GreeterTimerClient, request *greeterTimer.TimerRequest) error { stream, err := client.TimeHello(context.Background(), request) if err != nil { log.Fatalf("could not submit request: %v", err) } for { batchResponse, err := stream.Recv() if err == io.EOF { return nil } if err != nil { log.Fatalf("error during batch recv: %v", err) return err } reportBatchResult(batchResponse) } } ``` ### 2.4.5: Generate the go protobuf+gRPC code In our `//proto:BUILD` file, we have a `go_proto_library` rule loaded from the rules_protobuf repository. Internally, the rule declares to bazel that it is responsible for creating `greetertimer.pb.go` output file. This rule won't actually *do* anything unless we depend on it somewhere else. ```python # File: //proto:BUILD load("@org_pubref_rules_protobuf//go:rules.bzl", "go_proto_library") go_proto_library( name = "go_default_library", protos = [ "greetertimer.proto", ], with_grpc = True, ) ``` The go client implementation depends on the `go_proto_library` as source file provider to the `go_binary` rule. We also pass in some compile-time dependencies named in the `GRPC_COMPILE_DEPS` list. ```python load("@io_bazel_rules_go//go:def.bzl", "go_binary") load("@org_pubref_rules_protobuf//go:rules.bzl", "GRPC_COMPILE_DEPS") go_binary( name = "hello_client", srcs = [ "main.go", ], deps = [ "//proto:go_default_library", ] + GRPC_COMPILE_DEPS, ) ``` ```bash ~/grpc_greetertimer$ bazel build //go:client ``` Here's what happens when we invoke bazel to actually build the client binary: 1. Bazel checks to see if the inputs (files) that the binary depends on have changed (by content hash and filestamps). Bazel recognizes that the output files for the `//proto:go_default_library` have not been built. 1. Bazel checks to see if all the necessary inputs (including tools) for the `go_proto_library` are available. If not, download and build all the necessary tools. Then, invoke the rule. 1. Fetch the `google/protobuf` repository and build `protoc` from source (via a cc_binary rule). 2. Build the `protoc-gen-go` plugin from source (via a go_binary rule). 3. Invoke `protoc` with the `protoc-gen-go` plugin with the appropriate options and arguments. 4. Confirm that all the declared outputs of the `go_proto_library` where actually built (should be in `bazel-bin/proto/greetertimer.pb.go`). 5. Compile the generated `greetertimer.pb.go` with the client `main.go` file, creating the `bazel-bin/go/client` executable. ### 2.4.6: Generate the java protobuf libraries The `java_proto_library` rule is functionally identical to the `go_proto_library` rule. However, instead of providing a `*.pb.go` file, it bundles up all the generated outputs into a `*.srcjar` file (which is then used as an input to the `java_library` rule). This an implementation detail of the java rule. Here is how we build the final java binary: ```python java_binary( name = "server", main_class = "org.pubref.grpc.greetertimer.GreeterTimerServer", srcs = [ "GreeterTimerServer.java", ], deps = [ ":timer_protos", "@org_pubref_rules_protobuf//examples/helloworld/proto:java", "@org_pubref_rules_protobuf//java:grpc_compiletime_deps", ], runtime_deps = [ "@org_pubref_rules_protobuf//java:netty_runtime_deps", ], ) ``` 1. The `:timer_protos` is a locally defined `java_proto_library` rule. 2. The `@org_pubref_rules_protobuf//examples/helloworld/proto:java` is an external `java_proto_library` rule that generates the greeter service client stub in our own workspace. 3. Finally, we name the compile-time and run-time dependencies for the executable jar. If these jar files have not yet been downloaded from maven central, they will be fetch as soon as we need them: ```bash ~/grpc_greetertimer$ bazel build java/org/pubref/grpc/greetertimer:server ~/grpc_greetertimer$ bazel build java/org/pubref/grpc/greetertimer:server_deploy.jar ``` This last form (having the extra `_deploy.jar`) is called an *implicit target* of the `:server` rule. When invoked this way, bazel will pack up all the required classes and generate a standalone executable jar that can be run independently in a jvm. ### 2.4.7: Run it! First, we'll start a greeter server (one at a time): ```bash ~/grpc_greetertimer$ cd ~/rules_protobuf ~/rules_protobuf$ bazel run examples/helloworld/go/server ~/rules_protobuf$ bazel run examples/helloworld/cpp/server ~/rules_protobuf$ bazel run examples/helloworld/java/org/pubref/rules_protobuf/examples/helloworld/server:netty ~/rules_protobuf$ bazel run examples/helloworld/csharp/GreeterServer INFO: Server started, listening on 50051 ``` In a separate terminal, start the greetertimer server: ```bash ~/grpc_greetertimer$ bazel build //java/org/pubref/grpc/greetertimer:server_deploy.jar ~/grpc_greetertimer$ java -jar bazel-bin/java/org/pubref/grpc/greetertimer/server_deploy.jar ``` Finally, in a third terminal, invoke the greetertimer client: ```sh # Timings for the java server ~/rules_protobuf$ bazel run examples/helloworld/java/org/pubref/rules_protobuf/examples/helloworld/server:netty ~/grpc_greeterclient$ bazel run //go:client -- -total_size 10000 -batch_size 1000 17:31:04 1001 hellos (0 errs, 8999 remaining): 1.7 hellos/ms or ~590µs per hello # ... plus a few runs to warm up the jvm... 17:31:13 1001 hellos (0 errs, 8999 remaining): 6.7 hellos/ms or ~149µs per hello 17:31:13 1001 hellos (0 errs, 7998 remaining): 9.0 hellos/ms or ~111µs per hello 17:31:13 1001 hellos (0 errs, 6997 remaining): 8.9 hellos/ms or ~112µs per hello 17:31:13 1001 hellos (0 errs, 5996 remaining): 9.2 hellos/ms or ~109µs per hello 17:31:13 1001 hellos (0 errs, 4995 remaining): 9.4 hellos/ms or ~106µs per hello 17:31:13 1001 hellos (0 errs, 3994 remaining): 9.0 hellos/ms or ~111µs per hello 17:31:13 1001 hellos (0 errs, 2993 remaining): 9.4 hellos/ms or ~107µs per hello 17:31:13 1001 hellos (0 errs, 1992 remaining): 9.4 hellos/ms or ~107µs per hello 17:31:13 1001 hellos (0 errs, 991 remaining): 9.1 hellos/ms or ~110µs per hello 17:31:14 991 hellos (0 errs, -1 remaining): 9.0 hellos/ms or ~111µs per hello``` ```sh # Timings for the go server ~/rules_protobuf$ bazel run examples/helloworld/go/server ~/grpc_greeterclient$ bazel run //go:client -- -total_size 10000 -batch_size 1000 17:32:33 1001 hellos (0 errs, 8999 remaining): 7.5 hellos/ms or ~134µs per hello 17:32:33 1001 hellos (0 errs, 7998 remaining): 7.9 hellos/ms or ~127µs per hello 17:32:34 1001 hellos (0 errs, 6997 remaining): 7.8 hellos/ms or ~128µs per hello 17:32:34 1001 hellos (0 errs, 5996 remaining): 7.7 hellos/ms or ~130µs per hello 17:32:34 1001 hellos (0 errs, 4995 remaining): 7.9 hellos/ms or ~126µs per hello 17:32:34 1001 hellos (0 errs, 3994 remaining): 8.0 hellos/ms or ~125µs per hello 17:32:34 1001 hellos (0 errs, 2993 remaining): 7.6 hellos/ms or ~132µs per hello 17:32:34 1001 hellos (0 errs, 1992 remaining): 7.9 hellos/ms or ~126µs per hello 17:32:34 1001 hellos (0 errs, 991 remaining): 7.9 hellos/ms or ~127µs per hello 17:32:34 991 hellos (0 errs, -1 remaining): 7.8 hellos/ms or ~128µs per hello ``` ```sh # Timings for the C++ server ~/rules_protobuf$ bazel run examples/helloworld/cpp:server ~/grpc_greeterclient$ bazel run //go:client -- -total_size 10000 -batch_size 1000 17:33:10 1001 hellos (0 errs, 8999 remaining): 9.1 hellos/ms or ~110µs per hello 17:33:10 1001 hellos (0 errs, 7998 remaining): 9.0 hellos/ms or ~111µs per hello 17:33:10 1001 hellos (0 errs, 6997 remaining): 9.1 hellos/ms or ~110µs per hello 17:33:10 1001 hellos (0 errs, 5996 remaining): 8.6 hellos/ms or ~116µs per hello 17:33:10 1001 hellos (0 errs, 4995 remaining): 9.0 hellos/ms or ~111µs per hello 17:33:10 1001 hellos (0 errs, 3994 remaining): 9.0 hellos/ms or ~111µs per hello 17:33:10 1001 hellos (0 errs, 2993 remaining): 9.1 hellos/ms or ~110µs per hello 17:33:10 1001 hellos (0 errs, 1992 remaining): 9.0 hellos/ms or ~111µs per hello 17:33:10 1001 hellos (0 errs, 991 remaining): 9.0 hellos/ms or ~111µs per hello 17:33:11 991 hellos (0 errs, -1 remaining): 9.0 hellos/ms or ~111µs per hello ``` ```sh # Timings for the C# server ~/rules_protobuf$ bazel run examples/helloworld/csharp/GreeterServer ~/grpc_greeterclient$ bazel run //go:client -- -total_size 10000 -batch_size 1000 17:34:37 1001 hellos (0 errs, 8999 remaining): 6.0 hellos/ms or ~166µs per hello 17:34:37 1001 hellos (0 errs, 7998 remaining): 6.7 hellos/ms or ~150µs per hello 17:34:37 1001 hellos (0 errs, 6997 remaining): 6.8 hellos/ms or ~148µs per hello 17:34:37 1001 hellos (0 errs, 5996 remaining): 6.8 hellos/ms or ~147µs per hello 17:34:37 1001 hellos (0 errs, 4995 remaining): 6.7 hellos/ms or ~150µs per hello 17:34:38 1001 hellos (0 errs, 3994 remaining): 6.7 hellos/ms or ~150µs per hello 17:34:38 1001 hellos (0 errs, 2993 remaining): 6.7 hellos/ms or ~149µs per hello 17:34:38 1001 hellos (0 errs, 1992 remaining): 6.7 hellos/ms or ~149µs per hello 17:34:38 1001 hellos (0 errs, 991 remaining): 6.8 hellos/ms or ~148µs per hello 17:34:38 991 hellos (0 errs, -1 remaining): 6.8 hellos/ms or ~147µs per hello ``` The informal analysis demonstrated comparable timings for c++, go, and java greeter service implementations. The c++ server had the overall fastest and most consistent performance. The go implementation was also very consistent, but slightly slower than C++. Java demonstrated some initial relative slowness likely due to the JVM warming up but soon converged on timings similar to the C++ implementation. C# has consistent performance but marginally slower. ## 2.5: Summary Bazel assists in the construction of gRPC applications by providing a capable build environment for services built in a multitude of languages. [rules_protobuf](https://github.com/pubref/rules_protobuf/) complements bazel by packaging up all the dependencies needed and abstracting away the need to call protoc directly. In this workflow one does not need to check in the generated source code (it is always generated on-demand within your workspace). For projects that *do* require this, one can use the `output_to_workspace` option to place the generated files alongside the protobuf definitions. Finally, rules_protobuf has full support for the [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) project via the [grpc_gateway_proto_library](https://github.com/pubref/rules_protobuf/tree/master/grpc_gateway#grpc_gateway_proto_library) and [grpc_gateway_binary](https://github.com/pubref/rules_protobuf/tree/master/grpc_gateway#grpc_gateway_binary) rules, so you can easily bridge your gRPC apps with HTTP/1.1 gateways. Refer to the [complete list of supported languages and gRPC versions](https://github.com/pubref/rules_protobuf/#rules) for more information. And... that's a wrap. Happy procedure calling! > Paul Johnston is the principal at [PubRef](https://pubref.org) > ([@pub_ref](https://twitter.com/pub_ref)), a solutions provider for > scientific communications workflows. If you have an organizational > need for assistance with Bazel, gRPC, or related technologies, > please contact pcj@pubref.org. Thanks!