Understanding gRPC: A Modern Communication Protocol for Microservices

ASHISH MULEYASHISH MULEY
6 min read

Introduction

Under this development unfolding context that currently centers on distributed systems, cloud-native apps, and microservices, the key decision of choosing a specific protocol on which data shall be transferred from one service to another becomes crucial. Now, let's say hello to gRPC, an open-source, language-agnostic, high-performance RPC (Remote Procedure Call) framework developed by tech giant Google. Its purpose is built to simplify service communication in a strongly efficient and scalable manner.

In this text we will explain what gRPC is, tell what the characteristics of its implementations are and underlined what makes gRPC different from the conventional REST APIs.

What is gRPC?

gRPC (gRPC Remote Procedure Call) is a modern RPC framework which allows the direct communication of services with each other through well-defined types for each interface. It is built over HTTP/2 and uses Protocol Buffers or simply Protos as the data serialization format.

Key Features of gRPC:

  1. HTTP/2 for Transport

    gRPC uses HTTP/2 as the base, which carries functionalities like server push, flow control, header compression, and multiplexing-the ability to send multiple requests and responses on the same TCP connection. The performance gain is better, especially in high-traffic conditions.

  2. Protocol Buffers for Serialization Protocol Buffers, often referred to as protobufs, are arguably one of the formats gRPC relies on as its binary serialization which as referenced is more efficient and faster compared to JSON in REST. Moreover, there is the possibility of maintaining backwards compatibility with older API versions thanks to protobuf.

  3. Strongly-Typed Contracts
    gRPC enforces the contract of clients and servers strictly using the types defined within .proto files, which contain request and response types of the service in question. The outcome is definitely typed and well-defined client-server interaction.

  4. Language Agnostic
    gRPC supports a wide range of programming languages, for example: Java, Python, C, Go, Ruby and Node.js, to name just a few. Thus, it can be applied in multilingual environments.

  5. Bi-Directional Streaming
    gRPC also supports different types of RPCs : unary, server-side streaming, client-side streaming and bidirectional streaming, which allows the flexibility of communication patterns.

gRPC vs REST:

FeaturegRPCREST (with JSON)
Transport ProtocolHTTP/2HTTP/1.1
Data FormatProtocol Buffers (binary)JSON (text-based)
SpeedFaster due to protobufSlower (JSON parsing)
StreamingBuilt-in supportRequires WebSockets or SSE
Strong TypingYes (proto files)No (schema-less)
Contract EnforcementYesNo
Language SupportMulti-languageLanguage agnostic

When Should You Use gRPC?

gRPC excels in some situations where fast performance, immediate communication, and effective data serialization are a must. Some perfect scenarios will be:-

  • It would also be quite fitting with microservices architecture due to its capabilities related to contract enforcement and bi-directional streaming.

  • gRPC, like most modern RPC systems, also reduces latency with HTTP/2 and Protocol Buffers. A good number of applications use it because many applications that require fast communication-say, finance or gaming applications-typically fall into this category.

  • Applications that depend on the constant refreshing of real-time data, such as chat applications, live sports broadcasts, or collaboration platforms, are found specifically suited for gRPC and also to its streaming capabilities.

An Example using gRPC

Let’s jump right into a simple example of how gRPC works with a basic setup in Python.

Defining the .proto file:

syntax = "proto3";

service Greeter {
    rpc SayHello(HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

Generating Code:

The protobuf compiler (protoc) is then applied to the defined .proto file for generating the client and server code in your favored language, thus eliminating so much boilerplate communication logic.

Implementing the Server (Python Example):

from concurrent import futures
import grpc
import greeter_pb2
import greeter_pb2_grpc

class GreeterService(greeter_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return greeter_pb2.HelloReply(message=f"Hello, {request.name}")

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    greeter_pb2_grpc.add_GreeterServicer_to_server(GreeterService(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()

if name == '__main__':
    serve()

Implementing the Client:

import grpc
import greeter_pb2
import greeter_pb2_grpc

def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = greeter_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(greeter_pb2.HelloRequest(name='World'))
        print(f"Greeter client received: {response.message}")

if name == '__main__':
    run()

This setup demonstrates how quick and easy it is to get a gRPC service running with minimal effort.

Implementing gRPC in Java

To better understand how you can implement gRPC, let's see how you can create a gRPC server and client using Java. Next, we will see how you might create a Python client to communicate smoothly with a Java server. This example illustrates the capabilities of gRPC in providing cross-language communication.

Defining the .proto File

We will use the same proto file created earlier to create a gRPC in Java.

Implementing gRPC server in Java:

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import greeter.GreeterGrpc;
import greeter.HelloReply;
import greeter.HelloRequest;

public class GreeterServer {
    public static void main(String[] args) throws Exception {
        Server server = ServerBuilder.forPort(50051)
                                     .addService(new GreeterServiceImpl())
                                     .build()
                                     .start();
        System.out.println("Server started, listening on port 50051");
        server.awaitTermination();
    }
}

class GreeterServiceImpl extends GreeterGrpc.GreeterImplBase {
    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
        String greeting = "Hello, " + request.getName();
        HelloReply reply = HelloReply.newBuilder().setMessage(greeting).build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

We then develop a Java client that sends a request to the gRPC server and receives a response.

Client Code:

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import greeter.GreeterGrpc;
import greeter.HelloReply;
import greeter.HelloRequest;

public class GreeterClient {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
                                                      .usePlaintext()
                                                      .build();
        GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
        HelloRequest request = HelloRequest.newBuilder().setName("World").build();
        HelloReply reply = stub.sayHello(request);
        System.out.println("Greeter client received: " + reply.getMessage());
        channel.shutdown();
    }
}

Making Python and Java Communicate

Now, let's make the Python client talk to the Java server. You will now see why the language-agnostic nature of gRPC is so valuable.

  • Start the Java Server:

    Run GreeterServer on port 50051

  • Adjust the python file to connect to localhost on port 50051

  •     import grpc
        import greeter_pb2
        import greeter_pb2_grpc
    
        def run():
            with grpc.insecure_channel('localhost:50051') as channel:
                stub = greeter_pb2_grpc.GreeterStub(channel)
                response = stub.SayHello(greeter_pb2.HelloRequest(name='World'))
                print(f"Greeter client received: {response.message}")
    
        if __name__ == '__main__':
            run()
    
  • Running the Python Client:

    Run the Python client program, and you should be able to see an interaction with the Java server, which will likely be successful.

The Java Server waits for incoming gRPC requests at port 50051 and will respond with a greeting.The Python Client establishes a connection to the Java server, issues a HelloRequest, and receives a HelloReply.

Challenges with gRPC

The advantage of advancing technology is that it has brought about numerous benefits, while a couple of obstacles exist:

  • Browser Support: Clients in browsers are not natively supported by gRPC since it uses HTTP/2. For communication in browsers you will need gRPC-Web.

  • It might be more difficult to learn for those teams, henceforth accustomed to REST APIs, from which they are expected to make a transition to gRPC and Protocol Buffers.

  • On the other hand, REST offers several debugging tools for HTTP/1.1 and JSON requests like Postman. The tooling environment of gRPC is rather less mature.

Conclusion

This is a high-performance communication protocol that's going to give quite a few advantages over traditional REST APIs-especially systems designed around performance and scalability. gRPC is becoming the modern application favorite, especially in applications for microservices, where it has built-in streaming capabilities along with great serialization by using Protocol Buffers and wide language support. However, it is critical to evaluate your project's needs and understand the trade-offs before making this move

BY: Leevan Herald, Ashish Muley, Nikita Supekar, Abheerav Patankar

468
Subscribe to my newsletter

Read articles from ASHISH MULEY directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

ASHISH MULEY
ASHISH MULEY