gRPC: A primer
Sarvika Technologies is a tech company building software solutions for its clients across the globe using bleeding-edge technologies. A lot of the time, the software components we develop need to communicate with each other to work as intended. This article shows why we chose gRPC to build the communication stack of Clowre – an industry-agnostic application runtime – and the trade-offs we identified.
What are RPCs, and the “problems” with them in modern applications
The problem of making processes communicate with each other is ancient and was solved using IPC (inter-process communication). According to Wikipedia, IPC refers to “the mechanisms an operating system provides to allow processes to manage shared data”. Lock files are a simple example of IPC mechanisms.
But what happens when the processes that want to communicate with each other are running in separate address spaces on different hosts? The problem of making processes like this communicate was solved using RPCs – Remote Procedure Calls. RPC is when a computer program executes a procedure that spans multiple address spaces. Typically, applications doing RPC are categorized as clients and servers. Any application requesting data is called a client, and the program responding to this request is called a server.
In modern applications, a huge amount of RPCs is done using HTTP-Based protocols like REST and SOAP. Even though the implementations around these protocols work amazingly well, they come with their own gotchas. In terms of computing resources, a significant chunk of CPU time is consumed in parsing or serde logic and can go as deep as DTD or JSON Schema validation. Similarly, in terms of network, body sizes can get significantly large when a complex message is serialized. The point is that it works well but generates a lot of overhead.
One can work around all of this. All you have to do is:
- Come up with a messaging protocol that doesn’t take up much space.
- Come up with a mechanism for validating that your serialized messages are correct.
- Come up with a way of ensuring that messages are relayed correctly to the intended parts of your application (something similar to path-based HTTP request handlers).
- Share your new unicorn with the tech community and convince them that it’s better to use it instead of existing protocols that have been around since Elden ages.
- Make a blood offering to Kath’tomik so that it stops tormenting your soul.
- Make your new protocol testable by spec.
- … confusion?
Sounds practical, right?
gRPC is a recursive acronym for gRPC – Remote Procedure Calls. It’s a high-performance RPC framework, initially developed at Google under the name Stubby. The serde aspect of gRPC is built on top of Protocol Buffers, which allows messages to be serialized into a compact binary format. These serialized messages are transmitted over the HTTP/2 protocol, which enables gRPC to utilize its multiplexing capabilities to implement cool stuff like bi-directional streaming RPCs. Using HTTP/2 also means that whenever browsers start fully supporting it, gRPC can be a framework applicable in the “last mile” of distributed computing.
To ease the implementation of application logic with gRPC, it:
- Lets programmers call methods directly on a remote server as if it were a local object.
- Lets programmers implement generated interface on servers to handle client calls.
- Lets client software has stubs with the same methods as the server.
- Lets clients and servers establish “RPC Contracts” with the help of Protocol Buffer schema files.
What problems are solved by Protocol Buffers
As I said, these things work well, but then the caveats start. How would you ensure that the XML message you’re receiving is structured the way you want it to be? Syntax errors can be identified by failed parsing, but the semantics are often left to be checked by the implementation code. And again, as I said, there are things like DTD validation or JSON schema validation and they work well, but they add to the list of “additional things to take care of”.
Protocol Buffers (called protobuf in the rest of the article) solve both of these problems! Developers can create a `.proto` file, which essentially is a definition of messages and RPC routines. A protobuf compiler can take this schema and generate code in your favorite language. This generated code implements the serde logic of the messages declared in the schema. As of today, the protobuf compiler can generate code for C#, C++, Dart, Go, Java, Kotlin, Node, Objective-C, PHP, Python, and Ruby. Additionally, there are a couple of community-maintained Rust crates that can do the same. Since the serde code for protobuf messages is generated using their schema files, a lot of semantic validation is done at the deserialization step itself! Additionally, messages serialized in the protobuf format are much smaller than traditional XML/JSON payloads.
Even if you don’t want to do gRPC, you can STILL use Protocol Buffers for serializing messages and transmit them over a channel of your choice (like AMQP). Remember, Protocol Buffers came out long before Stubby was conceptualized.
The Transport Layer
gRPC clients use channels to maintain connections to a gRPC server on a specified address (a host and a port). gRPC channels use HTTP/2 protocol under the hood. Using HTTP/2 allows channels to utilize binary framing to divide messages into smaller frames and speed up transmissions. There are many added benefits of using HTTP/2 like header compression and no head-of-line blocking, but that’s a tale for another day.
Using HTTP/2 also means losing full compatibility with web browsers. Though there is gRPC Web, a project attempting to bring gRPC functionality to the front. To do so, it communicates to a proxy using a protocol different from the native gRPC. True end-to-end gRPC between browsers and servers is not possible at this moment.
Apart from the well-known Unary RPCs – where clients send a single request and receive a single response – gRPC offers fully duplex bidirectional streams. Particularly, there are three ways of streaming with gRPC.
- Client-Side Streaming RPCs: The client sends a stream of messages and the server responds with a single message (or an error).
- Server-Side Streaming RPCs: The server returns a stream of messages in response to a client’s request.
- Bidirectional Streaming RPCs: The call is initiated by the client invoking the method and the server receiving the client metadata, method name, and deadline. The server can send back its initial metadata or wait for the client to start streaming messages.
To keep this article code-free, I will conveniently leave out implementation details 🙂
Like everything in the software world, gRPC has its own trade-offs, and it is wise to conclude with them! We gain low-latency RPCs, streaming, and high-performance messaging, but we lose browser support. Using Protocol Buffers with HTTP/2 gives us smaller message sizes and reduced network overhead, but it takes away the ease of inspecting messages.
Thanks for reading. Here’s some Stuck in the Sound for your enjoyment 🙂
Written by Jay
Jay is a SoftwareArchitect at Sarvika Technologies, who fell in love with coding while developing mods for online games. He believes that an individual is defined by mindset and not by degrees. The software quality is of prime importance to Jay, an approach that helps him look at the bigger picture and build sustainable & sophisticated software like CLOWRE.