Golang monorepo part 2
As I promised in the last post about How to golang monorepo, we will talk about how to use Grpc in golang + bazel and customize the bazel docker image setup a little bit.
Key Terms
Grpc is a RPC framework developed at Google, and if you are writing services then I highly recommend you use it. Please read this for introduction about grpc.
Protocol Buffers are a way of serializing data and sending from one service to another. If you are writing a service in a polyglot monorepo then you can’t go wrong with using protocol buffers because they are a language agnostic way of defining a message which can be used across different languages.
Write your first grpc service
We will continue in our monorepo. In the last post we wrote a hello service that was a regular http service. Http services work fine but we want to write a hello service with self documenting, strict contracts, so lets convert it to grpc:
In this file you have a defined a service Hello
on line 4, it has one method Greet
that takes in GreetingRequest
as input and outputs GreetingResponse
which are defined on line #8 and #12 respectively.
Now if you run the magic incantation like our last post bazel run //:gazelle
you get
Things to note here:
proto_library
is the bazel way to define a library of protocol buffers. Other language target can depend on this common target.go_proto_library
is the go implementation that will generate.go
files for the your proto implementation.- If you had other languages you would write those targets here, ex: you could write a
java_proto_library
Setting up WORKSPACE
In order for your bazel setup to know about proto compiler proto
and actually compile the above, you will have to add a snippet to your WORKSPACE
file like this.
Lets try compiling
To compile the hello_go_proto
target you will need couple of more dependencies, declared here.
Now this should work bazel build //protos/hello:hello_go_proto
That build command in the background actually generates .go
files and places them in bazel-bin
folder.
Implement the GRPC service
Implementing the grpc service is easy, you just implement the golang interface which you can find in the autogenerated code. Let’s change our old hello http server
Change our main.go
to construct hello
server and run on a port.
Couple of things to note:
- You can construct
grpc.Server
with multiple grpc options like adding a logging or tracing interceptor. In this snippet at line #7 I am not using any option right now. - Register the implementation to the interface in line #9
- We start a listener and call
grpc.Server
waitForShutdown
will callserver.GracefulStop
Run bazel run //:gazelle
and then you can build the service like this bazel build //services/hello
You can find the working version in this commit.
Manage bazel code
I briefly want to talk about how bazel code can be organized. You don’t have to follow this advice but know that in the futureWORKSPACE
file will look a lot shorter from now on.
If your golang code resides in a monorepo and you are following my post, you will have a pretty long(ish) WORKSPACE
file. Because there are going to be different customizations for different languages, let’s try to break up the WORKSPACE
file into modular pieces. We will separate rules_go
stuff into one package and docker
into another.
- repos.bzl for a package will have dependencies like
http_archive
andgit_repository
- def.bzl for a package will call the relevant functions to add the functionality
Then you call the methods from these two files into WORKSPACE
Here are two commits for moving golang stuff and docker bazel stuff
Customizing docker images
As I talked about in part 1, docker image that we produce for hello is built on top of distroless.
One of the features of distroless
is that you can’t bash into your container. It is actually a good security feature but at least in your dev environment I have had the need to shell into my container. To achieve this I decided to base a version of hello
image off of ubuntu:
Import ubuntu using container_pull
in our bazel/docker/def.bzl
. I am pulling here from one of the public repos of tensorflow but you can pull it from any other image repository you want. I would also recommend looking into alpine.
Now we change BUILD.bazel
for hello service
And now you should be able to build docker images that you can bash into.
➜ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
bazel/services/hello image_dev 9496338fcef9 25 hours ago 2.16GB
Lets bash in
➜ docker run -it --entrypoint /bin/bash 9496338fcef9root@ebe363b85118:/app/services/hello/hello.runfiles/__main__# cd /app/services/
root@ebe363b85118:/app/services# ./hello
You will notice that the binary is called hello
as opposed to a weird name like base_image.binary
which is what you would have gotten if you followed the last post. This is because I specified it here.
You can find this customization on this commit.