Sahan Serasinghe

Senior Software Engineer | Master of Data Science

Building a gRPC Server in Go

2022-02-25distributed systems 7 min read

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.

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.

  1. Generate the .proto IDL stubs.
  2. Write the business logic for our service methods.
  3. Spin up a gRPC server on a given port.

In a nutshell, we will be covering the following items on our initial diagram.

building-grpc-server-go-1.png

💡 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.

building-grpc-server-go-2.png

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.

bookshop.proto

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.

building-grpc-server-go-3.png

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.

main.go

...

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)
	}
}
...
  1. 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.
  2. 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.
  3. In the main method, we are creating a listening on TCP port 8080 with the net.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:5000 Inventory/GetBookList

💡 Note: gRPC defaults to TLS for transport. However, to keep things simple, I will be using the -plaintext flag with grpcurl so that we can see a human-readable response.

building-grpc-server-go-4.png

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.

building-grpc-server-go-5.png

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 ✌️

References

Loading...
Sahan Serasinghe - Engineering Blog

Sahan Serasinghe Senior Software Engineer at Canva | Azure Solutions Architect Expert | Master of Data Science at UIUC | CKAD