GRPC vs REST

GRPC vs REST

Time to Say Goodbye to RESTFUL APIs?

It is undeniable that RESTful HTTP APIs have dominated the software industry during the last two decades. They enabled communication between different applications, servers and devices, thus allowing the worldwide web and the mobile ecosystem to grow and offer services essential to most of us.

It is also true that it has not been a smooth ride. A RESTful API is just an API abiding to the Representational State Transfer (REST) architectural style constraints. REST is neither a protocol nor a tool set. We do interpret its requirements when designing and implementing an API in order to make it RESTful. The hard truth is that we often fail for two main reasons: it is hard to comply with all REST constraints; opinions vary from developer to developer and from team to team thus leading to different implementation which can hardly inter-operate.

This is where gRPC comes into play.

gRPC is an open source remote procedure call system developed by Google. It was built to replace its internal RPC infrastructure called Stubby with the idea of taking advantage of modern standards like SPDY, HTTP/2, and QUIC, and to extend its applicability to mobile, IoT, and Cloud use-cases. As you can imagine, it leverages the immense Google experience and operative magnitude.

In this article, we will focus on some principles and requirements of gRPC and compare them with REST highlighting advantages and disadvantages of both techniques.

API Design: Resources vs Services

The first task at hand when developing a RESTful API is to translate your domain into resources uniquely identified by a URL, e.g. /user. Then we map standard HTTP verbs POST, GET, PUT and DELETE to CRUD actions: POST for creations, GET for retrievals, PUT for updates and upserts, and finally DELETE for deletions.

The design process is straightforward when dealing with CRUD but things gets complicated when dealing with more articulated business functions. How can we lock a user for a given period of time?

This is the moment discussion will ensue within the team. An option would be to add a lockUntil property to our user representation and update the user whenever you want to lock it. You will still wonder if this approach is RESTful or if there’s some better option. The reality is that it feels unnatural and unnecessarily complex.

Let’s see how we would work the same scenario in gRPC.

gRPC is built on the core principle that we must use Services not Objects, and Messages not References. In other words, in gRPC we start the process by defining our services and their message types. We use clear interfaces that maps directly to our business functions and programming concept like interfaces or methods. No need for translations. gRPC is all about APIs, not resources. Our locking service can be easily defined as:

message LockUserRequest {
	required string id = 1;
	required int32 minutes = 2;
}
message LockUserResponse {
	required int64 lockExpiry = 1;
}
service UserService {
	rpc LockUser(LockUserRequest) returns (LockUserResponse) {}
}

Figure 1: gRPC Service and Message Definition

Note: Learn more about API Types and different API protocols.

Contract Definition

When creating RESTful web services backed by a formal contract, you can choose between two approaches: contract-last and contract-first. When using a contract-last approach, developers start by coding the API in their preferred language and later they auto-generate a contract from it which serves as documentation. This is probably the easiest approach for a small team. For larger teams, contract-first is typically more suitable. In this case, the contract of the API is defined upfront and some portions of the implementation are auto-generated from it. Each team can then work independently by sticking to the predefined contract.

In both cases, you will be left with having to choose which language and toolset to use to define and auto-generate contracts or code. There are a lot of valid options like Swagger, now known as OpenAPI, and RAML but once again there’s no predefined standard.

gRPC uses protocol buffers by default as their Interface Definition Language (IDL) and serialization toolset. It is strictly contract-first.

Services and messages are defined in proto files and the relative server and client stubs are auto-generated using the gRPC toolset in any of the languages or platforms supported: C++, Java, Python, Go, Ruby, C#, Node.js, Android, Objective-C, PHP and Dart.

Server and clients will be able to communicate seamlessly across languages and platforms. You can easily create a gRPC server in Java with clients in Go or Python. gRPC allows real interoperability out of the box, something which is incredibly hard to achieve between RESTful APIs implemented by different teams.

/**
 * This services extends the base implementation {@link QuoteServiceImplBase} autogenerated by protoc.
 * @author marcol
 *
 */
public class QuoteService extends QuoteServiceImplBase {
	@Override
	public void getQuote(Empty request, StreamObserver responseObserver) {
		responseObserver.onNext(radomQuote());
		responseObserver.onCompleted();
	}
	private Quote randomQuote() {
		return Quote.newbuilder().setQuote()("Sample").build();
	}
}

Figure 2: Sample gRPC server implementation

JSON vs PROTOBUF

While there is no predefined serialization strategy when developing a RESTful API, JSON is the most popular payload format and can be considered the de facto standard in the industry. Its main advantages are that it is easy to read and well supported in most languages.

{
  "name": "Marco",
  "nationality": "Italian"
  "age": 34 
}

Figure 3: Sample JSON payload

The main disadvantages of JSON are that it is not schema driven, it has no typing, and it’s not versioned. All these drawbacks lead to serialization issues which can have disastrous consequences, especially in microservice-oriented architectures where failures cascade. The serialization toolset needs to process the entire payload and infer the type of each property. Needless to say, it is very common for the inferred type to be wrong.
Protocol buffers (protobufs) are instead Google’s language and platform neutral serialization mechanism. The data structure is defined upfront and the code to handle serialization and deserialization is auto-generated by the toolset for free. It is strongly typed and extensible.

message User {
  required string name = 1;
  optional string nationality = 2;
  optional int32 age = 3;
}

Figure 4: Sample gRPC User message definition

As you can see, each field in the message definition has a type and unique number. Typing removes any guessing when serializing data. The numbers are used to identify the fields in the message binary format and should never be changed when in use. They are Google’s approach to API versioning. If you intend to add new fields in the future, you will simply assign them a new number. Existing clients and servers will be able to identify fields they know by their numbers and ignore the rest.

If we want to remove some fields from the message definition, we need to ensure that their field name and number are not reused in the future by using the reserved keyword as shown above. This is necessary to ensure retro-compatibility by disallowing type changes for a given field name or number.

message User {
  reserved 4, 7 to 10;
  reserved "first_name", "last_name";
}

Figure 5: Using the reserved keyword

SYNC vs ASYNC

HTTP RESTful APIs are synchronous and unary by default since they are built around the request-response model available in HTTP/1.x. The client sends a single request and waits until it receives a response. However, networks are inherently asynchronous and prone to failure. In a microservice-oriented design, a single business function might translate to many internal requests which impact performance and increase complexity by forcing developers to find clever ways to make their systems resilient.

Cascading Failure

Figure 6: Cascading failure in a microservice-oriented architecture

gRPC supports both asynchronous and synchronous processing by taking full advantage of HTTP/2. You can use it to perform Unary RPC, Server Streaming RPC, Client Streaming RPC or Bidirectional RPC. It virtually covers all imaginable use cases, from simple CRUD operation to high throughput streaming used in storage systems and big data contexts which requires the best possible performance.

Let’s evolve a simple Unary RPC service into a more powerful bidirectional stream.

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

Figure 7: Unary RPC service definition

This is pretty basic. Our rpc SayHello expects a single HelloRequest and returns a single HelloResponse.

If we wanted to define Server Streaming RPC we would simply change HelloResponse to a stream.

service HelloService {
  rpc LotsOfReplies (HelloRequest) returns (stream HelloResponse);
}

Figure 8: Server Streaming RPC service definition

Similarly, if we wanted to switch to Client Streaming RPC we would change HelloRequest to a stream.

service HelloService {
  rpc LotsOfGreetings (stream HelloRequest) returns (HelloResponse);
}

Figure 9: Client Streaming RPC service definition

Finally, we can utilize streams in both directions.

service HelloService {
  rpc BiDirectionalHello (stream HelloRequest) returns (stream HelloResponse);
}

Figure 10: Bi-directional streaming RPC service definition

Auth

The REST architectural style does not give guidance on authentication and authorization concerns. For years, APIs have been secured using custom and inefficient auth schemes. Lately, the advent of SAML 2.0, OAuth 2.0 and OpenID Connect 1.0 eased the standardization process improving security and interoperability across different providers. 

gRPC has fully integrated pluggable authentication. It has two built-in auth strategies: certificate based which makes uses of SSL/TLS certificates; and token based which leverages Google OAuth 2.0 tokens. It is also extremely easy to extend gRPC support for other authentication mechanisms. Its Authentication API revolves around the unified concept of a Credentials object. Developers can then leverage the Credentials Plugin API to plug in their own type of credentials.

Performance

gRPC is designed for both high-performance and high-productivity design of distributed application. Performance is key principle in gRPC and it is guaranteed by continuous benchmarking as part of its development workflow. Multi-language performance tests are run every hour on the master branch and the results are published at https://grpc.io/docs/guides/benchmarking.html 

gRPC Performance Dashboard

Figure 11: gRPC performance dashboard

Bridging the Gap: gRPC-REST Gateway

Protobufs are not human readable. This is possibly the biggest drawback of gRPC. Developer and quality assurance specialists which are used to inspect HTTP request and response payloads over the wire need to change debugging approach. Moreover, it is not possible to invoke an endpoint just by typing its address on the browser.

Invoke RESTFUL API

Figure 12: Invoking a RESTful API from the browser

This is not a minor concern. Simplicity is a key aspect of REST. Probably, it has been the main reason of its wide popularity and I believe it will be the reason REST will continue dominating the API space in the near future.

I am sure you are getting mixed up now. Up to now, we have been praising gRPC but now, we are saying that RESTful APIs are here to stay.

Well, it’s a fact. You cannot simply switch one day to the next to a new technology. There are several reasons why you wouldn’t do that. You need to maintain backward-compatibility and you might have invested time and money to create an ecosystem of tools and technologies revolving around RESTful APIs. Moreover, are your clients ready or interested to switch to gRPC?

Luckily enough, gRPC developers are well aware of this and they incorporated the concept of an HttpRule in the service definition. HttpRule defines the mapping of an RPC method to one or more HTTP REST API methods. The mapping specifies how different portions of the RPC request message are mapped to URL path, URL query parameters, and HTTP request body.

service Messaging {
  rpc GetMessage(GetMessageRequest) returns (Message) {
    option (google.api.http).get = "/v1/messages/{message_id}/{sub.subfield}";
  }
}
message GetMessageRequest {
  message SubMessage {
    string subfield = 1;
  }
  string message_id = 1; // mapped to the URL
  SubMessage sub = 2;    // `sub.subfield` is url-mapped
}
message Message {
  string text = 1; // content of the resource
}

Figure 13: HttpRule definition in a gRPC service

The same http annotation can alternatively be expressed inside the GRPC API Configuration YAML file.

http:
  rules:
    - selector: .Messaging.GetMessage
      get: /v1/messages/{message_id}/{sub.subfield}

Figure 14: HttpRule definition in a gRPC Configuration YAML file

This definition enables an automatic, bidirectional mapping of HTTP JSON to RPC.

Finally, the gRPC community has created the grpc-gateway plugin for protoc, the protobuf code generator tool. It reads the gRPC service definition with its custom HTTP options and automates the provision of the APIs in both gRPC and RESTful style at the same time.

gRPC Gateway Diagram

Figure 15: gRPC gateway high level diagram

You get the best of both worlds with little effort. This is particularly useful for services exposed to the World Wide Web. You can continue leveraging RESTful HTTP APIs for your client integrations, while translating their requests to gRPC for internal communication.

Cloud Native and Kubernetes

Cloud Native technologies have taken the industry by storm in the last decade. There is no technology that can survive the fierce competition without joining Cloud Native bandwagon. gRPC is no exception and it is an incubating project hosted by the Cloud Native Computing Foundation (CNCF). It boasts huge support from the open source community on GitHub and it is used by industry leaders such as Netflix, Cisco, Juniper and CoreOS.   However, gRPC integration with the omnipresent Kubernetes is not straight forward as many would expect. The reason is that gRPC traffic does not load balance properly across multiple Pods associated with the same Service. gRPC Requests Load Balancer Kubernetes  

Figure 16: Requests are not load balanced as expected in a Kubernetes setup

The reason why requests are not load balanced correctly out of the box is simply HTTP/2. gRPC is built on HTTP/2 which, as we know, is meant to reduce latency by multiplexing multiple requests over a single TCP connection. What is happening in the diagram above is that the gRPC client in Pod A is opening a TCP connection on its first request and it’s re-using it for the subsequent ones. While this behaviour reduces latency, it’s undesirable in a highly available and redundant system where we expect to spread the load across multiple instances of the same component.

A homemade solution to the problem would be to manage multiple connections in our client application. However, I always strongly advice against adding such concerns into our application. The best way to deal with this issue is to employ one of the leading service meshes, such as Istio and Linkerd. Both have full support for HTTP/2, hence gRPC.

Sidecar gRPC Requests Load Balancer Kubernetes

Figure 17: The service mesh sidecar restores proper load-balancing of gRPC requests

A service mesh generally works by adding a sidecar container within the pods hosting our applications. The sidecar container takes over the Pod’s network configuration and inspects all incoming and outgoing traffic. This way the service mesh is able to control where requests are dispatched and thus re-introduce the natural load-balancing behaviour we expect in such setups. The best thing about it is that we don’t have to add a single line of code or configuration in our applications. All it takes is just adding the mesh in our Kubernetes cluster.

Conclusion

gRPC and protocol buffers are Google’s answer to modern system requirements. They allow seamless communication between different applications, languages and platforms. Different teams can agree on a contract and work independently while expecting smooth integration guaranteed by protobufs strong typing and cross-platform support. gRPC provides superior performance which is continuously monitored with hourly tests. Finally, gRPC is API centric. Its service definitions directly map to program methods and require no weird translation to a resource driven modelling.

Nevertheless, gRPC is not meant to replace RESTful APIs. At least not now. REST simplicity and its vast ecosystem cannot be substituted overnight, especially on public facing APIs. Luckily, this is not a problem since the gRPC community has provided means to bridge both worlds with little effort with the introduction of the grpc-gateway.

In conclusion, gRPC is a promising technology that has already gained a significant footprint on the API space. Google itself has promised to soon offer all its public APIs both in a RESTful fashion and gRPC. If you haven’t yet given gRPC a shot, it is definitely time to get familiar with it. This technology is particularly suited for microservice-oriented architectures where performance and reliability of application inter-communication is key to the success of the system.