Build Real-World Microservices with gRPC
Early microservices implementations leveraged Representational State Transfer (REST) architecture as the de-facto communication technology. However, RESTful services are often useful for external-facing services, which are directly exposed to consumers. As they are based on conventional text-based messaging (JSON, XML, CVS over HTTP, etc.), which are optimized for humans, these are not ideal choices for internal service-to-service communication.
Rather, using a text-based messaging protocol, we can leverage a binary protocol that is optimized for inter-service communication. The Cloud Native Computing Foundation’s gRPC (gRPC Remote Procedure Call) is an ideal choice for inter-service communication since it uses protocol buffers as the binary data interchange format for inter-service communication.
When we build multiple microservices with different technologies and programming languages, it is important to have a standard way to define service interfaces and underlying message interchange formats. gRPC offers a clean and powerful way to specify service contracts using protocol buffers. Therefore, gRPC is probably the most viable solution for building communication between internal microservices.
In this article, we will take a closer look at why gRPC is a great choice for building inter-microservices communication.
Fundamentals of gRPC
With gRPC, a customer can directly call methods on a server application on a different machine as if it were a local object. gRPC is based on the foundations of conventional Remote Procedure Call (RPC) technology but implemented on top of the modern technology stacks such as HTTP2, protocol buffers etc. to ensure maximum interoperability.
gRPC natively supports the ability to define a service contract using the gRPC Interface Definition Language (IDL). So, as part of the service definition, you can specify the methods that can be invoked remotely and the data structure of the parameters and return types.
Figure 1. illustrates the use of gRPC with an online retail application as part of an inventory and product-search service. The contract for the Inventory service is defined using gRPC IDL, which is specified in the inventory.proto file. So, a developer for the inventory service should first define all the business capabilities using the service and then generate the service side skeleton code from the proto file. Similarly, the client side code (stub) can be generated using the same proto file.
Since gRPC is programming-language agnostic, you can use heterogeneous languages to build services and clients. In this example, we have generated the Inventory service code using Ballerina (ballerina.io) and the client-side code using Java. You can try out this example using this source code on GitHub.
The service contract of the inventory(inventory.proto) is shown below.
The service contract is easy to understand and can be shared between the client and the service. If there’s any change to the service contract, both the service and client side code has to be regenerated.
For example, the following code snippet shows the generated code of the gRPC service for Ballerina. For each operation that we have in the gRPC service definition, the corresponding Ballerina code is generated. (Ballerina provides out-of-the-box capabilities to generate the service or client code with “ballerina grpc –input inventory.proto –output service-skeleton –mode service” or “ballerina grpc –input inventory.proto –output bal-client –mode client”).
For the client side, the product-search service, which is a Java (Spring Boot) service, is again generated from the gRPC service definition of the Inventory service. You can use the maven plugin to generate the client stub for the Spring Boot/Java service (the client code is embedded in the Spring Boot service). The client code that invokes the generated client stub is shown below.
Communication Under the Hood
When the client invokes the service, the client-side gRPC library uses the protocol buffer and marshals the remote procedure call, which is then sent over HTTP2. On the server side, the request is un-marshaled and the respective procedure invocation is executed using protocol buffers. The response follows a similar execution flow from the server to the client.
The main advantage of developing services and clients with gRPC is that your service code or client side code doesn’t need to worry about parsing JSON or similar text-based message formats (within the code or implicitly inside the underlying libraries such as Jackson, which is hidden from service code). What comes in the wire is a binary format, which is unmarshalled into an object. Also, having first-class support for defining a service interface via an IDL is a powerful feature when we have to deal with multiple microservices and ensure and maintain interoperability.
A Pragmatic Microservices Use Case with gRPC
Microservices-based applications consist of multiple services and are built with a variety of programming languages. Based on the business use case, you can pick the most appropriate technology to build your service. gRPC plays a very important role in this polyglot architecture. For example, let’s further extend our online retail use case for something more realistic. As shown in Figure 2, product-search service communicates with multiple other services, which are built using gRPC as the communication protocol. So, we can define the service contract for each service:inventory, electronics items, clothing items, etc. Now, if you want to foster a polyglot architecture, you can generate service skeletons using different implementation technologies.
Figure 2 illustrates the inventory service with Ballerina lang, the electronics service with Golang and the clothing service with Vert.x (Java). The client side can also generate a stub for each of these service contracts.
A closer look at the microservices communication styles in Figure 2 shows gRPC is used for all internal communication, while the external-facing communication can be based on REST or GraphQL. When we use REST for external-facing communication, most of the external clients can consume the service as an API (leveraging API definition technologies such as Open API) because most of the external clients will know how to communicate with an HTTP RESTful service. Also, we can use technologies such as GraphQL to allow consumers to query the service based on the specific client needs, which cannot be facilitated with gRPC.
Therefore, as a general practice, we can use gRPC for all synchronous communications between internal microservices. Other synchronous messaging technologies such as RESTful services and GraphQL are more suitable for external-facing services.