Implementing gRPC using Go
gRPC is a new communication protocol built over http2. This article will not cover basics of gRPC instead we will only be talking about implementing gRPC using Go. To know more about the basics, you can go here.
Why use gRPC and not HTTP?
- It’s built over Http2, which makes it faster than Http1.1
- gRPC uses bytes to transport data, this saves bandwidth.
- gRPC’s abstraction is such that communication between services looks like a function call.
- gRPC uses auto generated code, so all the heavy lifting is done for you already.
To get started you will need the below:
protoc: code generation
Go Plugin: protocol compiler.
Here is the set-up information.
Step 1: Setting Things Up
We would be building a distributed cache using gRPC. You don’t have to worry about the technicalities of cache. This is all about how to setup a gRPC between 2 services.
To get things rolling, we need to create a .proto file. Protoc compiler will use this file to generate the go code. This is what my proto file looks like.
https://gist.github.com/Prashant-sharma3012/66b5e7f60d46f28ff52c7d53f18b32fb#file-connector-proto
Syntax Explanation
syntax = "proto3";
package connector;
Syntax tells protoc about the version of probuf
package defines the package of generated go code
option go_package = "../connector";
This defines the location of generated code. In this case it is located in the connector folder. This path is relative to your proto file.
message Request {
string Key = 1;
string Value = 2;
string KeyToDelete = 3;
}message Response {
string Body = 1;
}
gRPC request and response structures.
service ConnectorService{
rpc AddToCache(Request) returns (Response);
rpc RemoveFromCache(Request) returns (Response);
rpc GetFromCache(Request) returns (Response);
rpc ReplaceInCache(Request) returns (Response);
}
To use gRPC we need to define a service. A service includes all RPC calls. In our case we are defining 4 calls that client can make to the server. each line / function in service has 4 parts to it.
rpc AddToCache(Request) returns (Response);
AddToCache: Method name
Request: Defines the request format structure
Response: Defines what this rpc call will return
Once you have your proto file, use PROTOC to generate go code. Below is how we generate code from the proto file
protoc --go_out=plugins=grpc:connector connector/connector.proto
plugins=grpc is the golang code generator plugin we got from github and the last parameter indicates the location of the proto file.
This command will create a file with same name as proto file but with extension .pb.go. This is what it looks like.
https://gist.github.com/Prashant-sharma3012/66b5e7f60d46f28ff52c7d53f18b32fb#file-connector-pb-go
Step2: Looking into the generated code.
Before we start implementing gRPC, few things that you can look for in the generated code
- Request and Response Structure: Both request and response structs have helper methods attached to them that we will be using to get data out of request and response. Methods that we will be using in our implementation are GetKey, GetValue and GetKeyToDelete. All of these are attached to Request struct. Both the structs have some extra fields (state, sizeCache, unknownFields)added to them. These fields are used by protobuf internally and we don’t need to worry about them.
- ConnectorServiceServer: This is the interface that server needs to implement to make RPC calls.
- RegisterConnectorServiceServer: This function registers server to grpc.
- NewConnectorServiceClient: This function registers client
Step3: Implementing server
To start off, first thing we need to do is implement the ConnectorServiceServer interface.
Create a server struct and add all interface methods to it. Once done we can use this struct to startup a gRPC server.
To start a server we need google’s grpc package.
google.golang.org/grpc
This is what my initialize server looks like
s is the struct that implements ConnectorServiceServer interface. Pass the instance of grpcServer and our struct to RegisterConnectorServiceServer.
Finally grpcServer.Serve(lis) to start listening for requests.
Step4: Implementing client
Implementing client is pretty easy and straight forward. All we need to do is dial into the port on which server is running. This will return us a connection of type *grpc.ClientConn. We can use this connection to directly call all our rpc functions we declared in proto file and implemented on server. This is how we make a connection.
var conn *grpc.ClientConn
conn, err = grpc.Dial(port, grpc.WithInsecure())
Using this conn object we can call our RPC functions.
This is what a RPC call looks like.
resFromWorker, err1 = conn.AddToCache(context.Background(), reqBody)
Here resFromWorker is rpc response. reqBody should match the Request struct we have defined in Proto file.
I understand this was a lot to take in and it takes a while to get hold of how things connect. You can look at the entire code that implements gRPC.