gRPC Basics: Why, When, and How?
Learn the basics of gRPC and see why it has grown in popularity, when it should be used and how. Ballerina, a network-aware programming language, is used.
Join the DZone community and get the full member experience.Join For Free
In this article, we will take a look at what gRPC is, when we are supposed to use it, and how exactly we can do so. We will also compare and contrast technologies used in similar domains. For code examples, we will be using Ballerina (referred to as Ballerinalang in the article), which is a programming language optimized for network-aware applications.
REST vs. RPC
The main requirement in question is the communication between programs in a network. In a standalone application, our communication is between in-process objects, where we do direct function/method calls. In this way, there is no requirement for communicating with an external program to get any required functionality. But as time moved on, we have started designing our applications to work in a more distributed manner. We would expose part of our business logic as an external service to be used by multiple consumers who are interested in doing so. So now, we need a communication mechanism when accessing these networked-endpoints.
A popular approach for accessing these remote services is using REST (Representational State Transfer). But when we talk about REST services, we tend to use this term loosely, where many of the service implementations do not fully adhere to the definition of a RESTful service. An important constraint of being a REST service is where our navigation through resources should be driven by hypermedia along with the knowledge of specific resource media types.
Figure 1: Persons REST API
Figure 1 shows an example of a REST API, which is used to expose information related to “persons”. The API starts with a single entry point, and it navigates through its resources to get the required information as needed.
But generally, when we try to design REST APIs, we use direct knowledge of resource locations in the APIs when accessing them. A standard used to define these HTTP resources is OpenAPI. It defines the resource paths and the methods when defining operations of an API.
In a REST API, the resources represent some type of information. So it’s more aligned with representing data entities. This data is accessed and manipulated with CRUD (Create, Retrieve, Update, Delete) operations. Thus, it’s not directly possible to express an action using a resource, but rather, we are only able to emulate them. For example, sending an SMS can be represented by an HTTP POST action to the resource “/sms”. So this is can be seen as adding a new resource to the “/sms” resource collection, which gets mapped internally to the action of sending an SMS to a specific location. So even though it’s possible to model the actions also in the same way, it is not the most natural thing to do. This is where the RPC (Remote Procedure Call) mechanism comes into play.
Figure 2: Admin RPC Service
Figure 2 shows an RPC service that contains several actions. You may have noticed that we have modeled our earlier data entity operations as actions as well, with addPerson() and getPerson(). By following such a specific convention, we can also represent our CRUD operations in an RPC approach.
Many RPC technologies are ranging from XML-RPC to SOAP to gRPC. These technologies have evolved into novel approaches for improving functionality, user-friendliness, and performance. gRPC has become one of the most popular technologies currently used for RPC. It is a binary protocol based on Google’s Protocol Buffers and works on top of the HTTP/2 transport. It promises high performance and features such as schema evolution and bidirectional streaming. If you find yourself modeling your API operations as actions most of the time, then it is a good sign that it should be modeled as an RPC solution. gRPC is supported widely by almost all of the mainstream programming languages, which makes it a popular choice for implementing APIs and their clients. Also, due to the underlying usage of HTTP/2, it follows an efficient network communication approach by multiplexing multiple streams through a single TCP connection. This allows the application to avoid the overhead when creating multiple network connections for separate communication channels. The programming model also supports the use of blocking and non-blocking communication, along with streaming features.
Let’s take a closer look at how gRPC works, and the tools and techniques that are required to implement it. The first aspect is the service definition using Protocol Buffers.
Protocol Buffers: A Primer
In an RPC service, the first step is to define the interface of the service. This is done using an IDL (Interface Definition Language) file. The IDL file of a gRPC service is provided using Protocol Buffers. ProtoBuf is a standard for serializing structured data. It provides the structures required for defining services, operations, and messages used in the service communication.
A proto file starts by mentioning the protobuf syntax version. The current latest version is “proto3”. This is mentioned in the following manner.
Now we can start defining a message. A message is a structure that contains some data fields. An example is shown below.
Here, we have represented a Person entity with the above protobuf message definition. The fields are defined by stating the type first, followed by the name of the field, and then we need to provide the field number. In this way, the first field id has a scalar type of int64, and a field number 1. The type can be a composite type as well, such as enumerations and other message types. A field number is a unique number that is used to identify the field in the encoded binary format. This makes sure, as long as we retain the same field numbers, the messages will be backward compatible. This is how ProtoBuf manages to support schema evolution.
A field can also be qualified as singular or repeated. This states that the message can have zero or one of these fields, or else, it can have zero or many of these fields, respectively. The default is singular. An example of this is shown below.
The AddRequest message contains a structure for an addition operation, which has an array of numbers to be added together. The AddResponse message contains a singular result field of type int64.
Commonly used ProtoBuf scalar types and their respective mapped Ballerinalang types are shown below.
- int32 - int
- int64 - int
- float - float
- double - float
- string - string
- bool - bool
- bytes - byte
A service is defined in ProtoBuf in the following manner.
The above snippet defines the RPC service AdminService with a method Add which takes in the AddRequest message and returns an AddResponse message. In this manner, we can add multiple methods to a single service. The following ProtoBuf definition in Listing 1 contains the full-service definition for the service shown in Figure 2.
Listing 1: “admin.proto” - Admin RPC Service ProtoBuf Definition
Now that we have the full ProtoBuf definition for our service. Let’s see how we can actually implement the service, and also write a client to invoke the implemented service.
gRPC Service and Client Implementation
Let’s start by creating a Ballerinalang project to implement our scenario.
Now change the directory into grpc-admin, and execute the following command to add two modules.
Now let’s execute the ballerina grpc command to create the gRPC service skeleton and the service stub/client respectively.
In the service module source directory, we would have the file AdminService_sample_service.bal automatically generated, which contains the service skeleton. In there, we can simply fill in the implementation of the service functions that are defined there. For example, the following is the service function generated for the add gRPC method.
In the same manner, the service stub/client is generated in the client module source directory. And the file AdminService_sample_client.bal contains a sample code instantiating the gRPC client to access the remote service. This would look similar to the following.
The object blockingEp contains the remote functions that correspond to the gRPC service methods. Figure 3 shows the VS Code code assist in listing the methods.
Figure 3: Admin Service Client Remote Functions List
Let’s see how to complete the implementation of the service function add.
Here, we have used two remote function calls send and complete from the caller object. The caller represents the actual client who is sending requests to the service. So we respond to the client using this object. The send remote function can be called more than once. This is due to the streaming functionality that is available with gRPC. After we possibly do multiple send calls, we can notify the completion of this session by calling the complete remote function. In this situation, our add invocation only consists of single return value to the client, so after a single send invocation, we immediately complete the call.
The full implementation of the service can be seen in Listing 2.
Listing 2: Admin RPC Service Implementation
Now that we have the service implementation fully done. We can take a look at how we can call these service methods using a client. We will be completing the code automatically generated at AdminService_sample_client.bal in the client module. Listing 3 shows the completed gRPC client code, which invokes all the methods we defined in our service.
Listing 3: Admin RPC Client Implementation
In the code above, the remote function calls return a tuple value which contains the service response value and the gRPC header values. Here, we are ignoring the header values and simply accessing the response value of the tuple value.
Now we have the full code completed for both the client and the service. Let’s take a look at how the executions of these will happen.
From the Ballerinalang project root directory, we can build and run both modules. First, let’s start the gRPC service.
We can now see that our gRPC service is up and running at port 9090. Now let’s run the client module, which will try to contact active service.
Here we can see that the client has established a connection with our gRPC service, invoked the methods in the service, and received their respective response messages.
In this article, we have gone through various approaches that are available when modeling services, mainly in the area of RESTful services and RPC. As gRPC is currently one of the most prominent RPC technologies, we looked at the basic building blocks of a gRPC service, starting from the service definition using Protocol Buffers, to code generation in Ballerinalang.
The full source code of the sample used in this article can be found here.
For more information on writing microservices in Ballerinalang, check out the following resources:
Opinions expressed by DZone contributors are their own.