Intro
In this article, we will create a simple web service with gRPC in Go. We won’t use any third-party tools and only create a single endpoint to interact with the service to keep things simple.
Motivation
This is the second part of an articles series on gRPC. If you want to jump ahead, please feel free to do so. The links are down below.
- Introduction to gRPC
- Building a gRPC server with Go (You are here)
- Building a gRPC client with .NET
- Building a gRPC client with Go
- Building a gRPC client with .NET
Please note that this is intended for anyone who’s interested in getting started with gRPC. If you’re not, please feel free to skip this article.
The plan
So this is what we are trying to achieve.
- Generate the
.proto
IDL stubs. - Write the business logic for our service methods.
- Spin up a gRPC server on a given port.
In a nutshell, we will be covering the following items on our initial diagram.
💡 As always, all the code samples documentation can be found at: https://github.com/sahansera/go-grpc
Prerequisites
This guide targets Go and assumes you have the necessary go tools installed locally. Other than that, we will cover gRPC specific tooling down below. Please note that I’m using some of the commands that are macOS specific. Please follow this link to set it up if you are on a different OS.
To install Protobuf compiler:
brew install protobuf
To install Go specific gRPC dependencies:
go install http://google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install http://google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
Project structure
There is no universally agreed-upon project structure per se. We will use Go modules and start by initializing a new project. So our business problem is this - we have a bookstore and we want to expose its inventory via an RPC function.
Since this talks about the creation of the server, we will call it bookshop/server
We can create a new folder called server
for the server and initialize it by so:
go mod init bookshop/server
In a later post, we will also work on the client-side of this app and call it bookshop/client
This is what it’s going to look like at the end of this post.
Creating the service definitions with .proto files
In my previous post, we discussed what are Protobufs and how to write one. We will be using the same example which is shown below.
A common pattern to note here is to keep your .proto
files in their separate folder, so that use them to generate the server and client stubs.
syntax = "proto3";
option go_package = "bookshop/pb";
message Book {
string title = 1;
string author = 2;
int32 page_count = 3;
optional string language = 4;
}
message GetBookListRequest {}
message GetBookListResponse { repeated Book books = 1; }
service Inventory {
rpc GetBookList(GetBookListRequest) returns (GetBookListResponse) {}
}
Note how we have used the option keyword here. We are essentially saying the Protobuf compiler where we want to put the generated stubs. You can have multiple option statements depending on which languages you are using to generate the stubs for.
💡 You can find a full list of allowed values at google/protobuf/descriptor.proto
Other than that, we have 3 messages to represent a Book entity, a request and a response, respectively. Finally we have a service defined called Inventory
which has a RPC named GetBookList
which can be called by the clients.
If you need to understand how this is structured, please refer to my previous post 🙏
Generating the stubs
Now that we have the IDL created we can generate the Go stubs for our server. It is a good practice to put it under the make gen
command so that we can easily generate them with a single command in the future.
protoc --proto_path=proto proto/*.proto --go_out=. --go-grpc_out=.
Once this is done, you will see the generated files under the server/pb
folder.
Awesome! 🎉 now we can use these subs in our server to respond to any incoming requests.
Creating the gRPC server
Now, we will create the main.go file to create the server.
...
type server struct {
pb.UnimplementedInventoryServer
}
func (s *server) GetBookList(ctx context.Context, in *pb.GetBookListRequest) (*pb.GetBookListResponse, error) {
return &pb.GetBookListResponse{
Books: getSampleBooks(),
}, nil
}
func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
s := grpc.NewServer()
pb.RegisterInventoryServer(s, &server{})
if err := s.Serve(listener); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
...
- We first define a struct to represent our server. The reason why we need to embed
pb.UnimplementedInventoryServer
is to maintain future compatibility when generating gRPC bindings for Go. You can read more on this in this initial proposal and on README. - As we discussed,
GetBookList
can be called by the clients, and this is where that request will be handled. We have access to context (such as auth tokens, headers etc.) and the request object we defined. - In the
main
method, we are creating a listening on TCP port 8080 with thenet.Listen
method, initialize a new gRPC server instance, register our Inventory service and then start responding to incoming requests.
Interacting with the Server
Usually, when interacting with the HTTP/1.1-like server, we can use cURL to make requests and inspect the responses. However, with gRPC, we can’t do that. (you can make requests to HTTP/2 services, but those won’t be readable). We will be using gRPCurl for that.
Once you have it up and running, you can now interact with the server we just built.
grpcurl -plaintext localhost:8080 Inventory/GetBookList
💡 Note: gRPC defaults to TLS for transport. However, to keep things simple, I will be using the
-plaintext
flag withgrpcurl
so that we can see a human-readable response.
How do we figure out the endpoints of the service? There are two ways to do this. One is by providing a path to the proto files, while the other option enables reflection through the code.
Enabling reflection
This is a pretty cool feature, where it will give you introspection capabilities to your API.
reflection.Register(gs)
Following screenshot displays some of the commands you can use to introspect the API.
grpcurl -plaintext -msg-template localhost:8080 describe .GetBookListResponse
Using proto files
If you don’t want to to enable it by code we can use the Protobuf files to let gRPCurl know which methods are available. Normally, when a team makes a gRPC service they will make the protobuf files available if you are integrating with them. So, without having to ask them or doing trial-and-error you can use these proto files to introspect what kind of endpoints are available for consumption.
grpcurl -import-path proto -proto bookshop.proto list
gRPCurl is great if you want to debug your RPC calls if you don’t have your client built yet.
Conclusion
In this article we looked at how we can create a simple gRPC server with Go. In the next one we will learn how to do the same with .NET.
Feel free to let me know any feedback or questions. Thanks for reading ✌️
Top comments (2)
Awesome post
Thanks for the kind words! 🙏