API Management / Networking / Open Source / Sponsored / Contributed

gRPC: A Deep Dive into the Communication Pattern

31 Aug 2021 9:35am, by

Danesh Kuruppu is a technical lead at WSO2, with expertise in microservices, messaging protocols and service governance. Danesh has spearheaded development of Ballerina’s standard libraries including gRPC, data and microservices framework. He has co-authored 'gRPC Up and Running' published by O’Reilly media.

If you have built gRPC applications and know about the communication fundamentals, you may already know there are four fundamental communication patterns used in gRPC-based applications: simple RPC, server-side streaming, client-side streaming and bidirectional streaming. In this article, I dive deeper into these communication patterns and discuss the importance of each pattern as well as how to pick the right one, according to the use case.

Before I discuss each pattern, I’ll discuss what they have in common, such as how gRPC sends messages between clients and servers over the network and how request/response messages are structured.

gRPC over HTTP/2

According to official documentation, the gRPC core supports different transport protocols; however, HTTP/2 is the most common among them. In HTTP/2, communication between a client and a server happens through a single TCP connection. Within the connection, there can be multiple bidirectional flows of bytes, which are called streams. In gRPC terms, one RPC call is mapped to a stream in HTTP/2. As shown in the figure below, the message passed between the client and the server breaks down to multiple frames and each frame is tagged with the stream ID. This allows multiple messages to be multiplexed in a single connection.

Image source: gRPC: Up and Running

You can see two different types of frames attached to streams ( i.e., Header and Data frames). This is similar to the HTTP protocol where request/response starts with headers followed by data content.

Request/Response Messages

In gRPC, the client triggers communication, which consists of request headers, binary messages (this is known as a length-prefixed message), and end-of-stream flag to notify the server that the client finished sending the content. In response, the server sends a response message, which also consists of headers followed by binary messages and trailers.

As we know how a gRPC message flows over the HTTP/2 connection, let’s discuss the communication patterns with coding samples. In this article, I will use the Ballerina language for the code samples.

Ballerina is an open-source language mainly focused on cloud native programming. If you haven’t installed Ballerina yet, you can download it from here.

Simple RPC

This is the simplest communication pattern by gRPC. Here, the client sends a single message to the server and receives a single message. As shown in the figure below, inside the stream, only one binary message is going in both request and response.

Let’s check what the code looks like on both the server and client side. Here, we are going to take the famous ‘Route Guide’ example on the gRPC official website.

The simple RPC method definition is extracted below; you can find the complete service definition in the Ballerina gRPC repository.

Here the ‘GetFeature’ method is used to obtain the feature at the given point location. The client sends the location details and receives the name of the feature. If there is no feature at the given location, a feature with an empty name is returned.

Using the service definition, we can generate the service stub and implement the logic of the GetFeature method.

In Ballerina, there is a built-in tool to generate the service and client stub files. You can refer to the gRPC tooling guide on the Ballerina website.

In the below code snippet, we have shown you the Ballerina implementation of the GetFeature method in the RouteGuide service.

Here you can see, we get a single ‘Point’ message as the input, and on the server-side, we get the corresponding ‘Feature’ and respond.

Now, let’s implement the client-side logic to invoke the ‘GetFeature’ method remotely. In gRPC, you can use any preferred language to implement the client side.

As shown in the above code snippet, the first step is to set up the connection to the server and initiate the client. Then, we can call the client’s ‘GetFeature’ method to invoke the remote method. In return, we get a ‘Feature’ message from the server.

In Ballerina, network calls are represented by the ‘→’ sign, which makes it easy to differentiate between network calls and local calls. (Refer to the Network Interaction sample for the consuming service.)

The check expression returns the function immediately without specifically handling it. (Refer to the Language sample for the check expression.)

The simple RPC pattern is straightforward to implement, so let’s move on to server streaming RPC, which is a more advanced pattern.

Server-Streaming RPC

Unlike in the simple RPC pattern, in server-streaming RPC, the server sends back a sequence of responses when a request is received from the client. This sequence of response messages are sent inside the same HTTP stream initiated by the client. As shown in the diagram below, the server waits until it receives the message from the client and sends multiple response messages as framed messages. At the end, the server concludes the stream by sending the trailing metadata with the call status details.

Let’s take the same Route Guide example to illustrate both the server and client-side implementation using Ballerina.

The server-streaming RPC method defined in the Route Guide service definition is extracted below. We’re going to demonstrate the server and client implementation using the below RPC method.

Here the ‘ListFeature’ method is used to obtain the Features available within the given Rectangular area. The client sends the point details of the rectangle and receives a list of available features as a stream.

As we did earlier, we can generate the server and client-side code from the service definition and build the logic of the ‘ListFeature’ method. The below code snippet shows how the ‘ListFeature’ method is implemented on the server side using Ballerina.

We get a single ‘Rectangle’ message as the input, and on the server-side, we filter out ‘Features’ within the ‘Rectangle’ and respond with a stream of ‘Feature’ messages.

Ballerina supports Integrated Query expressions, which specify the logic in SQL-like syntax to process the data. Refer to the integrated query guide on the Ballerina website for more details.

Now, let’s implement the client-side logic to invoke the ‘ListFeature’ method remotely.

Like the Simple RPC call, we first initialize the client and set up the connection with the server. Then, we can call the client’s ‘ListFeature’ method to invoke the remote method. The return is a stream of Feature messages. In Ballerina, this sequence of messages is represented by the Ballerina Stream object. We can iterate through the stream object and read the responses one by one.

Ballerina supports the stream type, which carries a sequence of values generated. (Refer to the Language sample for stream types.)

Now, we studied both simple and server-streaming RPC patterns. In both cases, the client initiates the stream by sending only one message and waiting for the response. The server sends either one message or multiple messages using the same stream. Let’s look at the client-streaming RPC pattern, which is opposite to the server-streaming pattern.

Client-Streaming RPC

In the client-streaming RPC pattern, the client sends multiple messages to the server and the server sends only one message in return. Similar to the patterns discussed above, all these messages are passed inside one HTTP stream initiated by the client. The figure below illustrates how multiple messages flow through the stream. This stream can last until the end of RPC.

Let’s take the same ‘Route Guide’ example to illustrate both the server and client-side implementation.

The client-streaming RPC method defined in the ‘Route Guide’ service definition is extracted below. We are going to demonstrate server and client implementation using the below RPC method.

Here, the ‘RecordRoute’ method accepts a stream of Points on a route being traversed and returns a RouteSummary when traversal is completed. The client sends points details on routes and receives a route summary when it is completed.

The below code snippet shows how the ‘RecordRoute’ method is implemented on the server-side using Ballerina Language.

As shown in the code snippet, the server receives client messages as a stream and iterates through the stream, computing the Route summary. Finally the server returns the summary report as a response.

In Ballerina, time-related operations are supported through the ballerina/time module. Refer to the API documentation for the time module for more details.

Now, let’s implement the client-side logic to invoke the RecordRoute method remotely.

Unlike in the other two RPC patterns, when the client’s RecordRoute() method is called, client streaming returns a streaming client. Using the streaming client, we can send and receive messages to/from the server. Once all the messages are sent, the client notifies the server about the completion.

Let’s look at the last communication pattern: bidirectional streaming RPC. This is a combination of the styles we discussed before.

Bidirectional Streaming RPC

In bidirectional streaming RPC, both the client and server send a stream of messages to each other. The client sets up the HTTP stream by sending header frames. Once the connection is set up, both the client and server can send messages simultaneously without waiting for the other to finish. The communication happens based on the logic of the gRPC client and server.

Let’s take the same ‘Route Guide’ example to illustrate both the server and client-side implementation.

The bidirectional streaming RPC method defined in the Route Guide service definition is extracted below.

Here, the ‘RouteChat’ method is used to accept a stream of RouteNotes on a route being traversed while returning RouteNotes from other users. The client sends the point details on routes and receives details of the same points from other users.

The below code snippet shows how the ‘RouteChat’ method is implemented in Ballerina.

As shown in the code snippet, the server receives client messages as a stream and iterates through the stream, sending RouteNode messages collected from other users. Here, the server uses the caller object to send messages to the client instead of returning values from the remote method. This is because the server needs to send messages simultaneously rather than waiting for the client request to complete.

In the Ballerina gRPC module, there are two options to send responses to the client: one is to return the value from the remote function, and the second is to use the caller object passed into the remote function. We can only use either one of these options.

Now, let’s implement the client-side logic to invoke the RouteChat method remotely.

This is similar to the client streaming pattern, when the client’s ‘RouteChat()’ method is called. It returns a streaming client. Using the streaming client, we can send and receive messages to/from the server. In client streaming, only one response message comes from the server after all messages have been sent by the client. However, in bidirectional streaming, server response messages can come simultaneously. So, you can start a new worker to read responses while sending messages to the server.

Ballerina has a unique concurrency model using strands. Refer to the Ballerina Concurrency Guide for more details.

With that, we covered all the communication patterns supported by gRPC. When you are building a gRPC-based application, it is important to analyze the business requirement and select the correct pattern. I hope the information you gathered from this article helps you to select the best pattern for what you need to accomplish.

Conclusion

gRPC is an inter-process communication technology that makes communication between microservices faster and more efficient. It supports four fundamental communication patterns: simple RPC, server-streaming RPC, client-streaming RPC and bidirectional RPC. Each communication pattern addresses unique practical problems when connecting multiple services to build real-world applications.

You can refer to the full implementation of the example we discussed here in the Ballerina gRPC Github repository.

Here are some additional resources to help you along the way.

The New Stack is a wholly owned subsidiary of Insight Partners. TNS owner Insight Partners is an investor in the following companies: Famous, Real.

Featured image via Pixabay

A newsletter digest of the week’s most important stories & analyses.