gRPC vs REST: know the differences
We are on the microservices era and that’s a good thing. Split a monolith in parts that have well defined roles, high coesion, loose coupling, and then you can do more frequent and safe deployments. Good, it’s all what we developers want, code being sent faster and safer to production.
But there’s a problem. When you have several microservices they need to talk with each other, and you need to worry with things like network latency, service availability and, of course, a communication pattern to interchange data.
When it comes to microservices communication, there’s a large amount of articles and code talking about REST, and certainly using REST is a good choice because:
- it’s simple
- it uses HTTP that we’re more familiar with
- you can get a lot of documentation and code examples on the internet
- programming languages have a high variety of libraries to simplify HTTP calls
In a REST approach, a microservice exposes a API that accepts HTTP calls in a path with a specific VERB, and a client can perform a request and do some action with it’s resources, like retrieving, updating or deleting.
There’s nothing wrong with REST, I love it and recommend for a lot of projects, but sometimes we want more performance, what means more requests/second, or maybe you want to build some streaming API for what REST is not the best choice.
gRPC is an open source framework to perform RPC (remote procedure calls) developed by Google. A remote procedure call is when a client invokes a procedure remotely. For example, imagine that you have a function in your preferred language that returns the famous “hello world”. With RPC, you can expose your function making it available to other systems to call it over network. A client, written in any language, can call your function and receive the “hello world” string as result.
Some benefits of gRPC include:
- it uses HTTP/2 which is more powerful than HTTP/1.1
- data is usually serialized with protocol buffers, a compacted binary format that improves data throughput
- is language agnostic, what means that client does not need be written in the same server language
- it supports unidirectional and bidirectional streaming
Let’s build a gRPC server and a client to demonstrate how gRPC works. We will use two different programming languages and also create a corresponding REST method to compare with gRPC method. Our sever will be build in Go and our client in Node.js.
First of all, we need to install all dependencies that include:
- gRPC lib
- Protocol Buffers V3
- protoc plugin for Go
Installing gRPC lib:
$ protoc plugin for Go
Installing Protocol Buffers V3:
Get the last release of protoc.zip from https://github.com/google/protobuf/releases, unzip and put
bin folder in your
$ protoc --version
Install the protoc plugin for Go:
go get -u github.com/golang/protobuf/protoc-gen-go
With all dependencies installed, you can create a new Go project in your $GOPATH folder.
Now we need to define our .proto file, where our messages and services will be described in Protocol Buffer format. In our example, we will create a remote method that returns the sum of two values. Our .proto file, that will be used by our server and our client, needs to describe everyting to make our method available remotely.
Create a calculator/calculator.proto file with the following content:
In this file, we are defining a service Calculator with one RPC method called Sum that accepts a Numbers parameter and returns a Result parameter. Both parameters are defined as a message, a special syntax of Protocol Buffers. We always need to create a message for our input and output parameters, we can’t just say that our method accepts two integers with type int32. Also, we are using Protocol Buffers version 3, as described on the first line.
From this file we will use protoc tool to generate a source file containing all stubs. Stub is a implementation of a method described in a proto file, and is responsible to generate binary code in Proto Buffer format. We don’t need to care about what is being generated, we only want to use the generated code.
$ protoc -I calculator/ calculator/calculator.proto --go_out=plugins=grpc:calculator
Now, you should have a file calculator.pb.go in your calculator folder. Take a look at this file to see what kind of code was generated.
With our stub generated, we need to create our gRPC server that accepts remote connections to our stub. Also, we will implement a REST method that does the same.
Create a main.go file with the following content (imports were omited):
When you run the file, you will have a gRPC server running at port 50051 and a HTTP server running at port 8080:
$ go run main.go
2018/11/07 17:35:15 HTTP Server started at port 8080
2018/11/07 17:35:15 gRPC server listening at port :50051
The function Sum() is doing what is expected to do. It receives a parameter Numbers, the same defined in our .proto file, but now the type Numbers is a struct defined in file generated from protoc tool. We can access both fields (Number1 and Number2) with getters methods , compute the sum and return a Result object. Note that the function has a pointer receiver to a Server object that will be used as our gRPC server.
The startGrpcServer function creates our server as a TPC server and register our Server object that now has a pointer to a Sum method. For this registration we use the RegisterCalculatorServer generated by protoc tool.
Good, now our server is ready to receive gRPC and HTTP calls. Let’s build a Node.js gRPC client.
Create a project with npm init and install two required packages with NPM:
$ npm install grpc @grpc/proto-loader
Copy the calculator.proto file into your project folder (the same file we created in the Golang server). We will not need to use a tool to create an implementation from our .proto file, the code will be generated dynamically.
Now create a client.js file with following content:
This client uses the grpc and proto-loader Node.js libs to perform a gRPC request on a remote server. The loadSync function loads a .proto file from a specific path and the loadPackageDefinition loads the calculator package definition (we define a packaged named Calculator in our proto file).
Now, we need to load our Calculator service that has the Sum method. This can be made through Calculator constructor from our package definition, passing our gRPC server as first parameter. In our case, server will be running at localhost:50051.
We are ready to call our gRPC method and for this we created a simple benchmark that perform n calls, where n is passed as first argument in command-line, and calculates the requests/second performed. Let’s try it with 10000 requests:
$ node client.js 10000
Starting benchmarking with 10000 requests
RPC Total time: 963 ms - 10384 requests/s
Nice. Our client can perform more than 10K requests per second, what is awesome. gRPC is really fast.
Let’s do the same with REST. Our server has a GET /sum method that does exactly the same, and we will use Apache ab to benchmark.
First of all, install Apache AB. Use your package manager like apt-get or yum in case of a Linux distribution. On a Ubuntu Linux, you can type this:
$ sudo apt-get install -y
Now, with our server still running, let’s do 10000 requests with this command:
$ ab -n 10000 localhost:8080/sum?number1=10&number2=20
Requests per second: 8065.43 [#/sec] (mean)
Here I got about 8K requests/second, what is still fast.
In this article, we did a simple gRPC server with one method and demonstrated that it can be called from any language that implements gRPC.
If you need maximum performance, less CPU consumption or a streaming API, give gRPC a try. In my tests, gRPC was about 20% faster but it can vary according to your benchmark tool.
If your API will be called from a browser or you don’t need to worry about performance, you can use REST with no problem.
I hope you enjoy.