gRPC: A Game-Changer in Cross-Platform Communication

Namrata
4 min readMar 2, 2024

--

gRPC (Google Remote Procedure Call) is an open-source high-performance remote procedure call (RPC) framework developed by Google. It enables client and server applications to communicate transparently and build connected systems. It’s designed to work across a variety of host languages and can be used to build distributed systems.

gRPC provides features like authentication, load balancing, logging, and monitoring out of the box. It uses Protocol Buffers (protobuf) as its interface definition language, which means you define the services and message types your application will use as .proto files. This allows you to define your service once and then generate client and server stubs from the .proto files in a variety of languages.

gRPC supports four types of service methods:

  • Unary RPCs where the client sends a single request to the server and gets a single response back.
  • Server streaming RPCs where the client sends a request to the server and gets a stream to read a sequence of messages back.
  • Client streaming RPCs where the client writes a sequence of messages and sends them to the server, again using a provided stream.
  • Bidirectional streaming RPCs where both sides send a sequence of messages using a read-write stream.

Overall, gRPC is a robust, efficient, and versatile framework for building scalable microservices and APIs.

How does it work?

gRPC works by defining a service in a .proto file, specifying the methods that can be called remotely with their parameters and return types. Here’s a simplified explanation of how it works:

  • Defining the Service: You start by defining the service interface in a .proto file. This includes defining the methods to be invoked remotely with their request and response types.
  • Generating the Code: Once you have the .proto file, gRPC’s protocol buffer compiler generates the client and server code in your chosen language. This generated code contains the method definitions, data structures, and code to serialize and deserialize the data structures.
  • Implementing the Server: On the server side, you implement the service interface and run a gRPC server to handle client calls. The gRPC server listens for requests from clients and executes the appropriate methods.
  • Creating the Client: On the client side, you use the generated code to create a client. The client can call the methods defined in the service interface as if they were local methods. Under the hood, gRPC takes care of sending the request to the server and returning the server’s response.
  • Communication: Communication between the client and server is done over HTTP/2. This allows for many requests to be in flight at the same time and for a single connection to be used for many client-server interactions.
  • Message Serialization: Messages sent between the client and server are serialized using Protocol Buffers by default. Protocol Buffers are a language-neutral, platform-neutral, extensible way of serializing structured data. They are smaller and faster than XML and JSON.
  • Support for Streaming: gRPC supports streaming semantics, allowing the server to push multiple messages in response to a client’s request or vice versa. This is done over the same connection, making it efficient.
  • Error Handling: gRPC allows for explicit error handling. Errors returned from the server are bubbled up to the client as exceptions.
  • Interceptors: gRPC has built-in support for interceptors, which allow for pre and post-processing of requests.
  • Other Features: Other features provided by gRPC include load balancing, health checking, and authentication and authorization.

How to define the service?

let’s imagine you want to create a simple service for a library. This service will have two methods: one for checking out a book and one for returning a book. Here’s how you might define this service in a .proto file:


proto


syntax = "proto3";

package library;

// The request message for checking out a book.
message CheckoutBookRequest {
string book_id = 1;
string user_id = 2;
}

// The response message for checking out a book.
message CheckoutBookResponse {
string confirmation_number = 1;
}

// The request message for returning a book.
message ReturnBookRequest {
string book_id = 1;
string user_id = 2;
}

// The response message for returning a book.
message ReturnBookResponse {
string confirmation_number = 1;
}

// The library service definition.
service Library {
// Checks out a book to a user.
rpc CheckoutBook(CheckoutBookRequest) returns (CheckoutBookResponse);

// Returns a book from a user.
rpc ReturnBook(ReturnBookRequest) returns (ReturnBookResponse);
}

file

In this example, each method has associated request and response message types. For instance, the CheckoutBook method takes a CheckoutBookRequest message and returns a CheckoutBookResponse message. The CheckoutBookRequest message contains the ID of the book and the ID of the user, while the CheckoutBookResponse message contains a confirmation number. The ReturnBook method is similar.

This is a very basic example. In a real-world application, you might have more complex message types and more methods. But hopefully, this gives you a good idea of how to define a service in a .proto file

How to create server?

This example assumes that you’ve already defined your gRPC service in a .proto file and generated the corresponding Java classes using the protobuf compiler.

you’ll need to implement the service. Let’s say you’re implementing the Library service from the previous example:

import library.LibraryGrpc;
import library.LibraryOuterClass;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
public class LibraryService extends LibraryGrpc.LibraryImplBase {
@Override
public void checkoutBook(LibraryOuterClass.CheckoutBookRequest request, StreamObserver<LibraryOuterClass.CheckoutBookResponse> responseObserver) {
// Implement your logic for checking out a book here.
// For now, we'll just return a dummy confirmation number.
LibraryOuterClass.CheckoutBookResponse response = LibraryOuterClass.CheckoutBookResponse.newBuilder()
.setConfirmationNumber("1234")
.build();

responseObserver.onNext(response);
responseObserver.onCompleted();
}

@Override
public void returnBook(LibraryOuterClass.ReturnBookRequest request, StreamObserver<LibraryOuterClass.ReturnBookResponse> responseObserver) {
// Implement your logic for returning a book here.
// For now, we'll just return a dummy confirmation number.
LibraryOuterClass.ReturnBookResponse response = LibraryOuterClass.ReturnBookResponse.newBuilder()
.setConfirmationNumber("5678")
.build();

responseObserver.onNext(response);
responseObserver.onCompleted();
}
}

In this example, the LibraryService class extends the base class generated by the protobuf compiler (LibraryGrpc.LibraryImplBase). Each method implementation receives a request object and a StreamObserver object for sending the response.

The @GrpcService annotation is part of the grpc-spring-boot-starter library and makes sure that Spring Boot recognizes this class as a gRPC service.

Please note that you need to replace the library package name with your own package name.

--

--

Namrata
Namrata

Written by Namrata

Engineering @Microsoft A software developer writing her daily bits . https://www.linkedin.com/in/namrataagarwal5/

No responses yet