DRPC: A Lightweight Alternative to gRPC?

DRPC is a lightweight buffer-based protocol based on gRPC. It removes some of the complexity of gRPC and can be implemented in fewer lines of code. Written in the Go language, DRPC stands for Decentralized Remote Procedure Call. Developed by Storj Labs, DRPC has fewer dependencies and supports a wide range of functionalities.

Benchmarks

Let’s see how fast DRPC is compared to gRPC. These are the benchmarks mentioned on the DRPC GitHub repository:

MeasureBenchmarkSmallMediumLarge
gRPCDRPCdeltagRPCDRPCdeltagRPCDRPCdelta
time/opUnitary30.2µs8.6µs-71.60%38.0µs11.1µs-70.88%1.33ms0.63ms-52.30%
Input Stream878ns759ns-13.54%2.85µs2.00µs-29.69%508µs249µs-51.08%
Output Stream862ns757ns-12.18%2.76µs1.99µs-27.92%487µs239µs-50.94%
Bidir Stream9.81µs3.30µs-66.38%14.8µs4.9µs-66.69%1.31ms0.55ms-58.41%
speedUnitary70.0kB/s230.0kB/s+228.57%54.0MB/s185.3MB/s+243.44%791MB/s1658MB/s+109.62%
Input Stream2.29MB/s2.64MB/s+15.37%721MB/s1026MB/s+42.21%2.06GB/s4.22GB/s+104.32%
Output Stream2.32MB/s2.64MB/s+13.67%743MB/s1031MB/s+38.74%2.15GB/s4.39GB/s+103.75%
Bidir Stream200kB/s604kB/s+201.87%138MB/s415MB/s+200.20%799MB/s1920MB/s+140.44%
mem/opUnitary8.37kB1.29kB-84.59%21.8kB7.7kB-64.81%6.50MB3.16MB-51.38%
Input Stream399B80B-79.96%7.09kB2.13kB-69.97%3.20MB1.05MB-67.16%
Output Stream309B80B-74.13%6.98kB2.13kB-69.53%3.20MB1.05MB-67.17%
Bidir Stream1.02kB0.24kB-76.40%14.4kB4.3kB-69.99%6.52MB2.10MB-67.74%
allocs/opUnitary1697-95.86%1719-94.74%4039-97.76%
Input Stream111-90.91%122-83.33%1212-98.35%
Output Stream91-88.89%102-80.00%1172-98.29%
Bidir Stream413-92.68%445-88.64%2725-98.16%

The Origins of gRPC

To get a fuller understanding of what DRPC is trying to accomplish, let’s first understand the origins of its predecessor, gRPC.

gRPC is an open-source remote procedural call system introduced by Google back in 2015. This protocol uses HTTP2 as an underlying transporting mechanism, and it uses protocol buffer as a message format.

From SOAP to REST

But, why does gRPC even exist when we have technologies like GraphQL, REST, WebSockets? To answer that, we must take a step even further back.

SOAP was very popular back in the 1990s. It’s similar to RPC, where you establish communication between two parties — the client and server. The client must have a library that understands SOAP and the server also requires a library to parse the XML.

In SOAP, the client and server have to agree on a schema for the message structure to communicate with each other. But, developers had mixed feelings about SOAP, as it had bloated XML. Many hated it because the schema was very rigid.

From REST to GraphQL

Therefore, REST emerged as a preferred method, as it has a better representation of state transfer that allowed developers to use whatever schema they wanted. To complement REST, JSON became a defacto data format, largely due to the popularity of JavaScript and the ease of JSON.

Then GraphQL came along, which addressed the over-fetching under-fetching problems of REST API calls. GraphQL is also a request-response-based protocol like REST and SOAP but lowers the number of necessary requests.

Bidirectional Communication

The REST, GraphQL, and SOAP paradigms still didn’t solve one problem: bidirectional communication. In the above three protocols, the client initiates communication. But what if the server needs to initiate communication too? To solve this problem, WebSockets were introduced and became a standard. Now every modern browser supports WebSockets.

But WebSockets didn’t have a format per se, they’re just a bunch of bytes, and you can do anything you want. The idea of “you can do anything you want” became popular, and that’s why WebSockets also became popular.

But there’s a big problem with all the above protocols no matter which one you choose. The client that sends the notification and the server that receives the communication must both agree. How do they agree? You need a client library!

A Standard Client Library for RPC

For example, if you use REST API, do you know what your library is? It’s likely an HTTP library. If you’re building a web application, then, in that case, your browser becomes the client, and it’s managing that library now.

Having a client library is a huge responsibility, but why do we care? If it’s all handled by the browser, it doesn’t matter, right? You simply make a fetch request or issue an XHR, then the browser establishes the HTTP communication with the server.

The browser negotiates the protocol, does the TLS for you, makes sure that the server supports the HTTP2, and if it doesn’t, falls back to HTTP1. It manages the headers, body, and streams. You just program a request, whether it’s a GET, PUT, or POST, and your browser takes care of the rest.

But let’s assume you’re not in the browser. You’re using a Python application, and you now need an HTTP client library or a SOAP library. You need to have a library installed to understand the protocol for the communication. To do that, it’s not your responsibility to manage the library. When you write your Python program, you import the request library, and someone maintains that library for you for free, right?

That’s the problem with libraries — they’re hard to maintain. You need to patch them to keep them updated with new releases. Let’s say HTTP 2.1 or HTTP 3 comes, then someone needs to update that request library to use the new protocols. And for every language, there’s a library that needs to be maintained.

Enter gRPC

This is where gRPC came into existence. Every time something new is invented, many libraries either come with it, or the older ones get updated. gRPC, which Google developed, aimed to standardize the whole thing (which is a second attempt by humans. The first was SOAP 😜).

Now, gRPC wants to unify the client libraries. Whereas previously, different developers or companies maintained the libraries for various programming languages, now, the gRPC community will do the hard work for all the popular programming languages. gRPC uses protocol buffers, which is a language-agnostic format. Therefore, if your client is Python and the server is based on C#, they can easily communicate because that’s the property of protocol buffer.

Why DRPC Then?

Now we understand the needs that gave rise to gRPC. So, why is there a need to reinvent the wheel, yet again? Let’s consider the problems of gRPC to understand what a solution like DRPC might solve.

Size of gRPC

gRPC is a very huge library. It has 40 dial options with 23 server options that allow you to make 13 call options. There are about 170K lines of Go for the gRPC library, making it one of the heaviest libraries on the internet. In comparison, DRPC can be used as a lighter open-source replacement for gRPC. DRPC does everything that gRPC can do in just 3000 lines of code (LOC). See the difference? 170K LOC vs. 3000 LOC.

Depreciation Issues

Service depreciation is another issue with gRPC. For example, the module WithBalancer was introduced when gRPC was invented. Later on, the WithBalancer was deprecated, and WithBalancerName was introduced. Then in 2019, WithBalancerName was deprecated, and WithDefaultServiceConfig was introduced. It’s 2021 now, and WithDefaultServiceConfig is still in EXPERIMENTAL mode. This makes it hard for developers to understand what to use in the long run.

Credit: Twitter

gRPC Is Hard to Debug

gRPC has about 170K lines of code, making it very hard to understand and debug. In contrast, DRPC comes with only 3000 lines, which is about 50X lesser than gRPC. Since it’s more lightweight and straightforward, DRPC is easier to debug.

Is DRPC An Improvement?

DRPC is an improvement over gRPC as it has fewer lines of code and is easier to debug. Another advantage of DRPC is that it can be easily integrated with other RPC clients like Twirp clients, and drpc-web can be used against drpchttp clients.

Implementing DRPC in native languages could bring advantages such as faster implementation and performance boosts. The packages for languages like C++, Rust, and NodeJS are being developed and have completed different stages of implementation. You can check out the DRPC implementations of the mentioned languages below: