A quick introduction to gRPC
gRPC is a an open source RPC (Remote Procedure Call) framework that can be used to create high performance distributed applications and services. It allows a client application to directly call methods of a server application, which is deployed in another machine, as if it were available in client machines. It can also be used to connect clients, (such as devices, mobile apps and browsers) to backend services.
Since gRPC supports multiple programming languages and platforms, a client written in one language (e.g. Java) can communicate with a server written another language (e.g. Go) seamlessly. This behaviour allows multiple services to talk to each other without any additional effort, thus making it a good candidate for micorservices style architecture.
In this article, let’s try to understand gRPC at a high level.
This article is intended for those who are relatively new to gRPC. If you are already familiar with gRPC, feel free to glance through. You might find something interesting. :-)
Do you know what does gRPC stand for? gRPC Remote Procedure Calls. Yep, you read that right. :-)
Types of RPCs
gRPC supports the following types of RPCs.
- In this scenario, client sends a single request to the server and server responds back with a single response. This is similar to a regular function calls.
Server streaming RPCs
- In this case, client sends a request to the server and gets a stream to read a sequence of messages back. Client continue to read from the returned stream until there are no additional message to read. gRPC ensures that the message ordering withing the RPC call is kept intact.
Client streaming RPCs
- Here, client writes a sequence of messages and sends them to the server, using a stream. After finishing writing messages, client waits for the server to read the messages and return a response.
Bidirectional streaming RPCs
- In this scenario, both server and client send a sequence of messages using a read-write stream. The two streams operate independently; so clients and servers can read and write in any order they like. gRPC ensures that the order of messages in each stream is maintained.
Following diagram depicts how gRPC server and clients typically interact.
gRPC supports a broad spectrum of languages such as C/C++, C#, Dart, Go, Java, Node.js, Objective-C, PHP, Python and Ruby. This helps developers to choose the right language depending on their use cases.
gRPC is built around a few design principles. It prefers services and messages over objects and references. This approach helps to avoid the challenges related to distributed objects. It is indented to work with common internet infrastructure, with a consideration that network traffic could be patchy. Each layer in the stack is expected to evolve independently.
Services with different message types and encodings are expected to work seamlessly. Streaming support is an integral aspect of gRPC. Both asynchronous and synchronous processing of sequence of messages exchanged between client and server are supported. Support for cancellation and timeouts are built-in, which would help to reclaim resources in cases of expensive operations.
For more details on the design principles, refer here.
The fundamental building block in gRPC is a service. The service definition contains methods that can be called remotely, it’s arguments and expected return types. By default, gPRC used Protocol Buffers as the Interface Definition Language (IDL).
Using Protocol Buffers, we can define the service interface as well as structure of payload message. (gRPC can also be configured to use other message formats such as JSON, XML and Thrift. But, for performance reasons, it is better to stay with Protocol Buffers.)
Before proceeding further, let’s take a quick look at Protocol Buffers.
Protocol Buffers, also known as protobuf, are mechanisms to serialize structured data. It is much smaller and faster compared to the likes of XML and JSON. Structure of the data can be defined once; reading and writing data to and from various data streams can be performed subsequently. It also supports many programming languages and platforms.
Structure of the data is defined in a .proto file by specifying required (and optional fields) along with their data types. Then, by using Protocol Buffer compiler (known as protoc), additional support files can be generated. These files provides functions required to set/get the fields, serialize message to output stream and parse message from input stream. For example, if we choose Go as the language, the protoc compiler will generate additional files with .pb.go extension. (We’ll see more of this in action when we try to build a sample program.)
To know more on Protocol Buffer, refer here.
Now, let’s explore the life cycle of a gRPC call at a high level.
- When client invokes a stub method, server is notified that the RPC has been invoked. The client’s metadata for the call along with the method name is also shared.
- Server may send back it’s metadata or wait to receive client’s request message.
- Once server receives client’s request, it performs required activities at server side.
- Once the processing is completed, server returns the response along with status code. If the response status is OK, client gets the response and completes the call at client’s side.
Server streaming RPC
This is similar to the unary RPC, with a few differences. In this case, server sends back a stream of messages in response to client’s request. After all messages are send, server completes it’s processing by sending server-side status details. Once client receives server’s status details, client completes it’s processing as well.
Client streaming RPC
This is similar to unary RPC, with a difference that the client sends a stream of messages. The server responds with a single message along with it’s status details, after receiving all of the client’s messages.
Bidirectional streaming RPC
In this case, client initiates the call by invoking the method. Server receives client metadata and method name. Server may send it’s initial metadata back or wait to receive client’s streaming message. There would be two streams in this case. One from client to server and vice versa. How these two are managed is application specific.
Though gRPC suits for many use cases as mentioned above, it definitely is not a one-size-fits-all kind of a solution. Let’s try to briefly take a look at two other popular choices, which are in the same space. (Please note that, this is not an apples-to-apples comparison mainly due to the different nature of the choices. Also, a detailed comparison warrant it’s own dedicated article. The intention here is to merely indicate availability of other options.)
REST (Representational State Transfer) is an architectural style that uses standard HTTP methods such as GET, POST, PUT and DELETE to operate on. Resources are exposed as URIs, which can be used by client application to perform read, write and delete operations. Though it supports various data formats, JSON is the most common format used.
To know more, check here.
GraphQL is a query language for the API, typically served over HTTP via a single end point. Data is represented in a graph, instead of columns and rows. GraphQL allows caller to structure returned data explicitly in the query itself. Typically, JSON is the format used for data exchange.
To know more, check here.
So far, we’ve covered basics of gRPC and it’s features. Now, wouldn’t it be cool to see it in action by creating and executing a simple client-server program?
Well, let’s head over to my article available here where we’ll discuss how to write a simple client-server program using gRPC.