10 min read

gRPC Multiclient With Java And Python

gRPC Multiclient With Java And Python
“Nothing in life is more important than the ability to communicate effectively.”– Gerald R. Ford

What is gRPC?

gRPC (Google Remote Procedure Call) is an open-source framework that facilitates communication between different applications and services. It allows developers to define service interfaces and message types using Protocol Buffers (protobuf), a language-agnostic binary serialization format developed by Google. With gRPC, developers can define the methods and data structures that a service provides and generate code for different programming languages, enabling seamless interoperability between systems.

What is RPC?

At its core, gRPC employs the concept of remote procedure calls (RPC), where a client application can invoke methods on a remote server as if they were local function calls. The communication between client and server is handled transparently by gRPC, abstracting away the complexities of network communication. gRPC uses HTTP/2 as the transport protocol, which provides advantages such as multiplexing multiple requests over a single connection, efficient binary serialization, and support for bidirectional streaming, making it highly efficient and performant.

Protocol Buffers

Protocol Buffers, also known as protobuf, is a language-agnostic and platform-neutral data serialization format developed by Google. They are designed to efficiently serialize structured data for communication between different systems, making it easier to exchange information.
Protocol Buffers use a language- and platform-independent schema definition language to describe the structure of the data. This schema acts as a contract between the sender and receiver, ensuring that both parties understand the data format. With protobuf, you define message types and their fields in a .proto file, specifying data types and optional attributes. The protobuf compiler then generates code in various programming languages, providing easy-to-use classes or structs that allow you to serialize and deserialize data. The main advantages of Protocol Buffers are their efficiency and compactness. The serialized data is smaller in size compared to other formats like XML or JSON, resulting in faster transmission and reduced bandwidth usage.
Below is an example of the difference in efficiency between both methods.

source: https://auth0.com/blog/beating-json-performance-with-protobuf/

HTTP/2 vs HTTP/1.1

To fully understand how gRPC and protobuf work, it is essential for us to understand the differences between HTTP/1.1 and HTTP/2.

HTTP/1.1 HTTP/2
Single connection Multiplexing
Plain text Binary Format
Headers are sent with each request Header Compression
Users can choose HTTPS Mandates HTTPS
Server Push
Prioritization

Multiplexing: HTTP/1.1 uses a single connection per request, which can lead to slow request blocks subsequent requests. In contrast, HTTP/2 supports multiplexing, allowing multiple requests and responses to be sent over a single connection simultaneously, reducing latency and improving performance.

Binary Format: HTTP/1.1 uses plain text for its messages, which can be verbose and inefficient. HTTP/2, on the other hand, uses a binary format for its messages, which is more compact and can be parsed more efficiently by machines.

Header Compression: In HTTP/1.1, headers are sent with each request and response, leading to redundant data transmission. HTTP/2 introduces header compression, where header fields are compressed and sent once as part of an initial request, and subsequent requests only include a reference to the compressed headers. This reduces overhead and improves efficiency.

Server Push: HTTP/2 introduces server push, which allows the server to proactively send additional resources to the client without waiting for explicit requests. This can help improve page load times by reducing the need for multiple round trips between the client and server.

Prioritization: HTTP/2 supports request prioritization, allowing the client to specify the importance of different requests. This enables more efficient resource allocation and ensures that critical resources are delivered quickly.

Security: While both HTTP/1.1 and HTTP/2 can be used over both plain HTTP and HTTPS, HTTP/2 mandates the use of encryption (HTTPS) by most modern web browsers, enhancing security and privacy.

API Types in gRPC

gRPC supports four types of communication patterns: unary, server streaming, client streaming, and bidirectional streaming.

Unary: The simplest and most common type of RPC in gRPC. In this pattern, the client sends a single request to the server and waits for a single response. The communication is synchronous, meaning the client holds until it receives the response from the server. Unary RPC is similar to traditional client-server interactions, where the client sends a request and waits for a response before proceeding.

Server Streaming: The client sends a single request to the server, but the server responds with a stream of messages. The server starts sending the messages as soon as it receives the request and continues sending until it completes or encounters an error. The client can read these messages one by one as they arrive. Server streaming is useful when the server needs to send a large amount of data or when the server needs to push periodic updates to the client.

Client Streaming: The client sends a stream of messages to the server. The server receives these messages as they arrive and processes them. After receiving all the client's messages, the server sends a single response back to the client. This pattern is useful when the client has a large amount of data to send or when the client wants to push a continuous stream of data to the server.

Bidirectional Streaming: Bidirectional streaming RPC allows both the client and server to send a stream of messages to each other. The client can send messages to the server while receiving messages from the server simultaneously. This pattern enables real-time communication and is suitable for scenarios where both the client and server need to send multiple messages and interact asynchronously.

gRPC vs REST

RESTgRPC
CommunicationUses HTTP/HTTPS for communicationUses HTTP/2 for communication
Data FormatTypically uses JSON or XML for data interchangeUses Protocol Buffers (protobuf) for data exchange
PerformanceLess efficient due to overhead and parsingMore efficient due to binary serialization
Communication PatternsSupports request/response (synchronous)(GET, POST, PUT, DELETE, etc.)Supports multiple communication patterns(Unary, Server Streaming, Client Streaming, Bidirectional Streaming)
Error HandlingUses HTTP status codes for error handlingUses gRPC status codes for error handling
Tooling EcosystemVast tooling ecosystem and widespread adoptionEvolving tooling ecosystem and growing adoption
CachingSupports caching of responsesDoes not inherently support caching
InteroperabilityGood interoperability with various systemsBest suited for internal services and microservices

Alright, Should I Always Use gRPC From Now On?  

well, no.
You should examine your use case before using gRPC over REST.
For example, here are some situations you SHOULD NOT use gRPC for.

Public-Facing APIs: If you're building public-facing APIs that need to be consumed by a diverse range of clients, such as web browsers, mobile devices, or third-party applications, gRPC may not be the most suitable choice. While gRPC does have HTTP/1.1 compatibility, it is primarily designed for use with HTTP/2, which may not be widely supported by all client platforms.

Extensive Caching: If your service heavily relies on caching responses at different levels, gRPC may not be the most suitable option. While gRPC does not inherently prevent caching, the binary nature of Protocol Buffers and the dynamic nature of streaming patterns can make caching more complex to implement and manage.

Maturity of Tooling and Ecosystem: Suppose your project relies on an extensive ecosystem of libraries, frameworks, and infrastructure components that are well-established in the REST world. While gRPC has gained popularity and continues to evolve, it may not have the same level of maturity, tooling, and community support as REST.

Simple or Lightweight Services: If your service is relatively small or straightforward, and doesn't require advanced features such as streaming or bidirectional communication, introducing the additional complexity of gRPC may not be necessary.

DEMO Time!

In this demo, we will create a bank server to retrieve user bank details.
The response will contain the user balance and its recent transactions.  
The gRPC server will be implemented in Java. We will create 2 clients to access our service, one in Java and one in Python. Find all the code on my GitHub page.

Open a new Maven project on your preferred IDE ( I use IntelliJ for this demo)

Setup pom.xml
These dependencies will allow us to auto-generate classes from the proto files we will create later on. pom.xml file

Create proto files

Create new folders at src/main/proto and src/main/proto/bank.
Inside the bank folder create 2 files:
user.proto and account.proto

Understand proto file structure:

  1. This section defines the Java properties for the proto file. (This section is the only one that varies from one language to another.)
  2. A message in a proto file is equivalent to object/class in Java.
    Here, we define an Account message that contains 2 variables in it.
    1. int32 -> a 32 bit integer
    2. repeated int32 -> a list of 32 bit integers.
    Notice, the variable name is only for human readability the proto compiler does not use it - it uses only the type and key (the number after the = sign).
    🎯 Fun Fact:  Since gRPC transmission is binary, smaller key numbers take less memory than large key numbers.
  3. This section defines a request and a response to use in our services section. The convention is to name the request/response as MESSAGE_NAME + Requset/Response.
  4. This section define our services, the rpc methods in the service section will be traslated directly to methods in our services to consume.
  5. Note: repeated scalars are packed by default on protobuf 3.

Clean + install

mvn clean install will generate all the boilerplate and classes for us automatically.
After the installation is complete you should be able to see all the generated files at the target folder.

Implement functionality at BankServerIml.java

  1. Our implementation must extend the base service implementation (a generated class) and we will override the methods and create our own logic.
  2. Extracting the user from the request, of course, we had to include the object in the request proto file in order to use it.  
  3. Create the result in String format (also defined in the proto file)
  4. Send the response with onNext(). With server streaming, this is also the place we will loop over the responses.
  5. complete and end the RPC call.

Create the actual Server - BankServer.Java

public class BankServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        System.out.println("Hello, im a gRPC server");

        // simple server at 50051
        Server server = ServerBuilder.forPort(50051)
                .addService(new BankServiceImpl())
                .build();

        server.start();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Received Shutdown Request");
            server.shutdown();
            System.out.println("Successfully stopped the server");
        }));

        server.awaitTermination();
    }
}

Notice how we have to inject the ServiceImplementaion Class to the Server.
We must add server.awaitTermination() for the server to stay alive.  

Create a Java Client - BankClient.Java

    public static void main(String[] args) {
        System.out.println("Hello I'm a gRPC client");

        BankClient main = new BankClient();
        main.run();
    }

    private void run() {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
                .usePlaintext() // This is needed to run on localhost (no ssl)
                .build();

        doUnaryCall(channel);
    }

run() method defines the server endpoint, we also need to use usePlainText() to debug locally without SSL, since gRPC uses SSL by default.  

now, we can define the unary method. Let's create an Account object and a User object.

    private void doUnaryCall(ManagedChannel channel) {
        // Blocking Client
        UserServiceGrpc.UserServiceBlockingStub userClient = UserServiceGrpc.newBlockingStub(channel);

        ArrayList<Integer> randomTransactionList = getSampleTransactionList();

        // Account message
        Account account = Account.newBuilder()
                .setBalance(sumTransactions(randomTransactionList))
                .addAllTransactions(randomTransactionList)
                .build();

        // User message
        User user = User.newBuilder()
                .setFirstName("Matan")
                .setLastName("Parker")
                .setId(123456789)
                .setAccount(account)
                .build();

        Withdrawal withdrawal = Withdrawal.newBuilder()
                .setRequestAmount(100)
                .build();

        // Build user request
        UserRequest userRequest = UserRequest.newBuilder()
                .setUser(user)
                .build();

        WithdrawalRequest withdrawalRequest = WithdrawalRequest.newBuilder()
                .setUser(user)
                .setWithdrawal(withdrawal)
                .build();

        // Send the request and save the response
        UserResponse userResponse = userClient.userDetails(userRequest);

        WithdrawalResponse withdrawalResponse = userClient.withdrawal(withdrawalRequest);

        System.out.println(userResponse.getResult());
        System.out.println(withdrawalResponse.getResult());

    }

Run the server and then the client.

implementing the Python Client.

First of all, we need to copy the proto files to the Python client directory.
Make sure you have grpcio and grpcio-tools installed. You can install them using
pip install grpcio grpcio-tools

Auto-generate classes:

python -m grpc_tools.protoc --proto_path=. proto/bank/user.proto --python_out=. --pyi_out=. --grpc_python_out=.

python -m grpc_tools.protoc --proto_path=. proto/bank/account.proto --python_out=. --pyi_out=. --grpc_python_out=.

After running these commands you should be able to see the generated classes

Create the Python client

import grpc

import proto.bank.user_pb2 as user_pb2
import proto.bank.user_pb2_grpc as user_grpc

if __name__ == "__main__":
    details_request = user_pb2.UserRequest()
    details_request.user.account.balance = 200
    details_request.user.account.transactions.insert(0, 10)
    details_request.user.account.transactions.insert(1, 20)
    details_request.user.account.transactions.insert(2, 30)
    details_request.user.account.transactions.insert(3, 40)
    details_request.user.id = 123456789
    details_request.user.first_name = "Matan"
    details_request.user.last_name = "Parker"

    withdrawal_request = user_pb2.WithdrawalRequest()
    withdrawal_request.withdrawal.request_amount = 100
    withdrawal_request.user.account.balance = details_request.user.account.balance
    withdrawal_request.user.first_name = details_request.user.first_name

    with grpc.insecure_channel('localhost:50051') as channel:
        stub = user_grpc.UserServiceStub(channel)
        # response = stub.User(user_pb2.UserRequest())
        details_response = stub.UserDetails(details_request)
        withdrawal_response = stub.Withdrawal(withdrawal_request)
        print(details_response.result)
        print(withdrawal_response.result)

Running the main.py script will yield the same results, as expected.

Lastly, Call gRPC Server With Postman  

  1. Click new and select gRPC

2. Enter the Server Address and import proto files

3. Import proto files one by one (add an import path if your proto files reference each other)

4. Select a method

5. Compose a message or click Use Example Message to quick start

6. Click Invoke to run the method

Summary

Wow, that was a really long one... What we saw here??
This blog post provides an overview of gRPC and its core concepts. It explains how gRPC facilitates communication between applications and services using Protocol Buffers for message serialization. The post compares gRPC with REST, highlighting the differences between the two. It also discusses the types of communication patterns supported by gRPC. We looked at use cases that are not suitable for gRPC and created a full demo using Java and Python.

As usual full code is available on my GitHub page.