An Introduction to gRPC
GRPC (Google Remote Procedure Call) is a modern, high-performance remote procedure call (RPC) framework that has become very popular for building distributed systems and microservices. In this post, I’ll give an overview of gRPC and demonstrate building a simple gRPC service.
Introduction:
Before getting into gRPC, let’s quickly define RPC. RPC stands for Remote Procedure Call. It allows calling a procedure or function on a remote system in a transparent way — as if it was a local function. The underlying communication and serialization is handled automatically.
GRPC (Google Remote Procedure Call) is a modern, high-performance remote procedure call (RPC) framework that has become very popular for building distributed systems and microservices…
Why gRPC?
gRPC was created to solve some of the problems with existing RPC frameworks and client libraries. Every time you build an RPC protocol, the client and server have to agree on things like data formats, error handling, etc. This means building custom client libraries, which is hard to maintain and patch over time.
gRPC solves this by using protocol buffers under the hood. Protocol buffers allow you to define schemas for your data in a language-neutral format. gRPC builds in support to auto-generate strongly-typed client libraries in over 10 languages from your protocol buffer definitions. This means you only have to maintain one set of client libraries.
On top of this, gRPC is high-performance since it’s built on top of HTTP/2. It supports bidirectional streaming, deadlines, cancellation, and compression — features that are much harder to build on top of a REST API.
Building a gRPC Service
To demonstrate gRPC, let’s build a simple to-do list service that supports creating and retrieving to-do items.
First, we define the service interface and data structures in a .proto
file:
service ToDoService {
rpc CreateTodo (TodoItem) returns (TodoItem) {}
rpc GetTodos (google.protobuf.Empty) returns (TodoList) {}
}
message TodoItem {
int32 id = 1;
string text = 2;
}
message TodoList {
repeated TodoItem items = 1;
}
Next on the server, we use the grpc
Node module to implement the service:
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');
// Load the proto definition
const packageDef = protoLoader.loadSync('todo.proto');
const grpcObject = grpc.loadPackageDefinition(packageDef);
const todoPackage = grpcObject.todoPackage;
// Implement the service
function CreateTodo(call, callback) {
// ...
}
function GetTodos(call, callback) {
// ...
}
// Create a server
const server = new grpc.Server();
server.addService(todoPackage.TodoService.service, {
createTodo: CreateTodo,
getTodos: GetTodos
});
// Start listening
server.bind('0.0.0.0:50000', grpc.ServerCredentials.createInsecure());
server.start();
Finally, on the client, we can call the service just like a local object using the auto-generated client stub:
// Create client
const client = new todoPackage.TodoService('localhost:50000',
grpc.credentials.createInsecure());
// Call service
const newTodo = {
id: 1,
text: 'Do laundry'
};
client.createTodo(newTodo, (err, res) => {
if (!err) {
console.log('Created todo', res);
}
})
And that’s it! The client can call remote methods on the gRPC service as if it was a local object, without needing to worry about the underlying protocol and communication.
Conclusion
gRPC makes it easy to define RPC services and auto-generate strongly-typed clients. Combined with protocol buffers for serialization, it’s an efficient yet simple way to build distributed systems. While there is a bit more setup complexity compared to REST, you gain a ton more flexibility for streams, bidirectional communication, and avoiding client library hell.