Site icon Vinsguru

gRPC Unary API

Overview:

In this tutorial, I would like to show you how to implement a gRPC Unary API in Java.

I assume that you have a basic understanding of what gRPC is. If not, read the below articles first.

  1. Protocol Buffers – A Simple Introduction
  2. gRPC – An Introduction Guide

gRPC Unary API:

Unary is a simple request and response model. A client sends the request to the server and the server will process the request and respond back. This call can either be a blocking synchronous call or a non-blocking asynchronous call. Let’s take a look both options.

Sample Application:

We need to implement a calculator application in which our back-end server has to find the factorial for the given number. To keep things simple, we will not be doing any error handling for now. It will be a separate article to explain in detail.

Protobuf – Service Definition:

Now we know what the business requirement is & what the client expects! So let’s create a service definition for this. findFactorial is going to be the method to be implemented on the server side. This service definition shows what type of input to be sent and what type of output to expect. By default if we do not use any stream keyword in the rpc, it is Unary.

syntax = "proto3";

package calculator;

option java_package = "com.vinsguru.calculator";
option java_multiple_files = true;

message Input {
  int32 number = 1;
}

message Output {
  int64 result = 1;
}

service CalculatorService {
  // unary
  rpc findFactorial(Input) returns (Output) {};
}

When we issue the below maven command, maven automatically creates the client and server side code using protoc tool.

mvn clean compile

For example, CalculatorServiceImplBase class in the below picture is auto-generated abstract class which needs to be implemented by the server for the above service definition. Similarly CalculatorServiceStub is the actual implementation class which client should use to make a request.

A simple service definition file does most of the heavy lifting already for the client server communication.

gRPC Unary API – Server Side:

Service Implementation: Let’s extend the abstract CalculatorServiceImplBase to add our implementation to respond to the findFactorial call. The implementation is very simple as shown here. The server will receive the Input type which we had defined using protobuf earlier.

import com.vinsguru.calculator.CalculatorServiceGrpc;
import com.vinsguru.calculator.Input;
import com.vinsguru.calculator.Output;
import io.grpc.stub.StreamObserver;

public class UnaryCalculatorService extends CalculatorServiceGrpc.CalculatorServiceImplBase {
    
    @Override
    public void findFactorial(Input request, StreamObserver<Output> responseObserver) {
        int input = request.getNumber();
        long result = this.factorial(input);
        Output output = Output.newBuilder()
                .setResult(result)
                .build();
        responseObserver.onNext(output);
        responseObserver.onCompleted();
    }

    private long factorial(int number){
        if(number == 0)
            return 1;
        return number * factorial(number - 1);
    }
    
}

Once the service implementation is done, Let’s add it to the server to serve the client calls. We are listening on port 6565. Start this server by invoking the main method.

public class CalculatorServer {

    public static void main(String[] args) throws IOException, InterruptedException {

        // build gRPC server
        Server server = ServerBuilder.forPort(6565)
                .addService(new UnaryCalculatorService())
                .build();

        // start
        server.start();

        // shutdown hook
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("gRPC server is shutting down!");
            server.shutdown();
        }));

        server.awaitTermination();

    }

}

Now our server is ready, up and running!

gRPC Blocking Unary Call:

Protobuf already has generated the client library. The client has to do the following to make a request and receive the response.

To demo this first with blocking stub, I am going to create a simple JUnit test class to act like a gRPC client. Do note that client can be anything. It could even be another microservice.

public class UnaryServiceTest {

    private ManagedChannel channel;
    private CalculatorServiceGrpc.CalculatorServiceBlockingStub clientStub;

    @Before
    public void setup(){
        this.channel = ManagedChannelBuilder.forAddress("localhost", 6565)
                .usePlaintext()
                .build();
        this.clientStub = CalculatorServiceGrpc.newBlockingStub(channel);
    }

    @Test
    public void unaryServiceTest(){
        // build the request object
        Input input = Input.newBuilder()
                .setNumber(5)
                .build();

        // receive the response
        Output output = this.clientStub.findFactorial(input);

        //check the result
        Assert.assertEquals(120, output.getResult());
    }

    @After
    public void teardown(){
        this.channel.shutdown();
    }

}

When we run this, we are able to successfully verify that we get the results as expected.

gRPC Async Unary Call:

If the request is time consuming, we can make this request to be completely non-blocking by using asynchronous stub as shown below.

public class OutputStreamObserver implements StreamObserver<Output> {

    @Override
    public void onNext(Output output) {
        System.out.println(
                "Received : " + output.getResult()
        );
    }

    @Override
    public void onError(Throwable throwable) {

    }

    @Override
    public void onCompleted() {

    }

}
public class UnaryServiceTest {

    private ManagedChannel channel;
    private CalculatorServiceGrpc.CalculatorServiceStub clientStub;

    @Before
    public void setup(){
        this.channel = ManagedChannelBuilder.forAddress("localhost", 6565)
                .usePlaintext()
                .build();
        this.clientStub = CalculatorServiceGrpc.newStub(channel);
    }

    @Test
    public void unaryServiceTest(){
        // build the request object
        Input input = Input.newBuilder()
                .setNumber(5)
                .build();

        // receive the response
        this.clientStub.findFactorial(input, new OutputStreamObserver());
    }

    @After
    public void teardown(){
        this.channel.shutdown();
    }

}

Now if we execute the client side code, the client does not wait for the response to come back. The StreamObserver implementation gets executed asynchronously when the response is received.

BloomRPC:

It is very very easy to test REST based APIs by using postman / web browsers. But testing gRPC might not look easy so far. It seems to require writing separate code. That is where BloomRPC comes into picture which provides the postman like experience. Download and install from the release page.


gRPC Course:

I learnt gRPC + Protobuf in a hard way. But you can learn them quickly on Udemy. Yes, I have created a separate step by step course on Protobuf + gRPC along with Spring Boot integration for the next generation Microservice development. Click here for the special link.


Summary:

We were able to successfully implement a gRPC server and invoke the unary API using both synchronous and asynchronous way . Lets explore streaming APIs in this article.

The source code is available here.

Happy learning 🙂

Share This:

Exit mobile version