Go and gRPC

Introduction :

In gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object, making it easier for you to create distributed applications and services.

gRPC is based around the idea of defining a service, specifying the methods that can be called remotely with their parameters and return types: On the server side, the server implements this interface and runs a gRPC server to handle client calls. On the client side, the client has a stub (referred to as just a client in some languages) that provides the same methods as the server.

gRPC diagram

gRPC and protocol buffers :

By default gRPC uses protocol buffers, a data format for serializing structured data, but it can be used with JSON as well.

Let’s go through protocol buffers. First you need to define the structure for the data you want to serialize in a proto file (with the .proto extension). Protocol buffer data is structured as messages. A message contains a series of name-value pairs called fields:

message Person {
string name = 1;
int32 id = 2;
}

We use the protocol buffer compiler, protoc, to generate the data access classes in a specific programming language from the proto file.

protoc --proto_path=proto --go_opt=paths=source_relative --go_out=out proto/*.proto

Let's break down the command above:

  • --proto_path is the path where the proto files reside. It is not necessary if you're the command from a parent directory of the folder that contains the proto files. for example, let's say we have the following structure:
  |--- src 
  |     |-- proto
  |     |     |-- file.proto
  |     |
  |     |-- out 
  • If you run protoc from the src directory, you don't need to specify the proto_path.
  • BUT, if you run the command from the out (which is not a parent directory of the proto directory), you'll need to specify the protoc_path.

The first thing is that all the code will be placed in inside the directorires specfied by the go_out and grpc_out variables. Another thing is that the location where the generated code placed will be specific to how the last (argument) path in the command relative to the proto_path: if the proto_path is a/b/c, the proto files are located in a/b/c/d/e, go_out is . and you're running the code from the out directory, then all the geneated code for the messages types will get placed in out/d/e. The same rule applies to grpc code.

The code generates simple accessors for each field, like name() and set_name(), as well as methods to serialize/parse the whole structure to/from raw bytes.

Now, let’s come back to gRPC. We can define gRPC services in ordinary proto files, with RPC method parameters and return types specified as protocol buffer messages. Note that both the parameters and the method are both mandatory.

// The greeter service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message contains the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

There are two versions of Protocol buffers: proto2 and proto3. In general you can use proto2, but the recommended one to use with gRPC is proto3 because it has a broader language support.

gRPCs and HTTP 2 :

Note that gRPC is built on top of HTTP 2, so it benefits from its advanced features, as it reduces the number of http connections and reduces latency, along with header compression and multiplexing.

gRPC diagram - gRPC with proxies

gRPC is compatible with many proxies and load balancers that use HTTP 2. Also, gRPC supports direct client side load balancing (without proxy) and can operate as well with service meshes.

gRPC diagram - gRPC with no proxies

Interceptors (“Middleware”) allow you to share common functionalities like authentication, logging and authorization across services.

RPCs method types :

  • Unary RPCs: the client sends a single request to the server and gets a single response back.
rpc SayHello(HelloRequest) returns (HelloResponse);
  • Server streaming RPCs: the client sends a request to the server and gets a stream to read to a sequence of messages back. The client reads from the returned stream until there are no more messages. gRPC guarantees message ordering within an individual RPC call.
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  • Client streaming RPCs: where the client writes a sequence of messages and sends them to the server, again using the provided stream. Once the client has finished writing the messages, it waits for the server to read them and return its response.
    Again, gRPC guarantees message ordering within an individual RPC call:
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
  • Bidirectional streaming RPCs where both sides send a sequence of messages using a read-write stream. The two streams operate independently, so clients and servers can read and write in whatever order they like: for example, the server could wait to receive all the client messages before writing its responses, or some other combination of reads and writes. The order of messages in each stream is preserved.
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

Server and Client code :

After defining a service in a .proto file service, how to generate the code for the gRPC server and client? gRPC uses a special gRPC plugin to generate code from the proto file.

protoc --proto_path=proto --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative --go_out=out --go-grpc_out=out proto/*.proto

gRPC users typically call these APIs on the client side and implement the corresponding API on the server side. On the server side, the server implements the methods declared by the service and runs a gRPC server to handle client calls. The gRPC infrastructure decodes incoming requests, executes service methods, and encodes service responses. On the client side, the client has a local object known as stub (for some languages, the preferred term is client) that implements the same methods as the service. The client can then just call those methods on the local object, and the methods wrap the parameters for the call in the appropriate protocol buffer message type, send the requests to the server, and return the server’s protocol buffer responses.

Made with
Passion
</>
</>
Passion
Made with