gRPC made easy with Quarkus

This article discusses how to create applications with the gRPC framework and Quarkus. We will reuse the sample Service definition from first Java gRPC application and run it as Quarkus REST application.

Defining the gRPC Service

Firstly, we recommend reading this article for an introduction to the gRPC framework: Getting started with gRPC on Java

We will be using the FileManager Service definition from our Java example which returns a list of Files available in a remote directory:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.mastertheboss.filesystem";
option objc_class_prefix = "HLW";

package filesystem;

service FileManager {
  rpc ReadDir (Directory) returns (FileList) {}
}


message Directory {
  string name = 1;
}


message FileList {

string list = 1;
}

Next, we will show how to run the above Service with Quarkus. There are two main benefits that Quarkus can bring to gRPC applications:

  • Quarkus can register your Services and Start a plaintext or SSL gRPC automatically
  • You can generate the the Java files from proto files with quarkus-maven-plugin

Creating the Quarkus project

To build an equivalent Quarkus application from our Proto file we will need to include the quarkus-grpc dependencies in a project. We also add the resteasy reactive dependencies to expose the gRPC with a REST Service:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-mutiny</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-grpc</artifactId>
</dependency>

Next, copy the above proto file in the folder src/main/proto as example.proto

The project set up is complete, Next, we will build the REST Endpoint to test our Service:

package io.grpc.example.filesystem;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;

import com.mastertheboss.filesystem.*;

import io.quarkus.grpc.GrpcClient;
import io.smallrye.mutiny.Uni;

@Path("/filesystem")
public class DemoGRPCEndpoint {

    @GrpcClient("filesystem")
    FileManagerGrpc.FileManagerBlockingStub blockingService;

    @GrpcClient("filesystem")
    FileManager service;

    @GET
    @Path("/blocking")
    public String helloBlocking(@QueryParam("dir") String dir) {
        FileList reply = blockingService.readDir(Directory.newBuilder().setName(dir).build());
        return reply.getList();

    }

    @GET
    @Path("/mutiny")
    public Uni<String> helloMutiny(@QueryParam("dir") String dir) {
        return service.readDir(Directory.newBuilder().setName(dir).build())
                .onItem().transform((reply) -> reply.getList());
    }

}

The most interesting part, is the @GrpcClient which is used to inject gRPC stubs in the Endpoint.
The first one, inject a blocking stub using the gRPC API

 @GrpcClient("hello")
 FileManagerGrpc.FileManagerBlockingStub blockingHelloService;

The second one, injects a service interface using the Mutiny API which represents streams receiving either an item or a failure (Uni)

 @GrpcClient("hello")
 FileManager helloService;

Coding the Service Implementation

So far we have exposed the gRPC stubs via REST API. We still need to provide the Service implementation for the method readDir. The following DemoGRPCService implements the method as part of the FileManager interface:

package io.grpc.example.filesystem;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import io.grpc.stub.StreamObserver;
import io.quarkus.grpc.GrpcService;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import com.mastertheboss.filesystem.*;

@GrpcService
public class DemoGRPCService implements FileManager {


    @Override
    public Uni<FileList> readDir(Directory req) {
        File f = new File(req.getName());
        if (!f.isDirectory())  {
            throw new RuntimeException(req.getName() + " is not a directory.");
        }
        String name = req.getName();

        String[] pathnames = f.list();
        StringBuffer sb = new StringBuffer();
          // For each pathname in the pathnames array
          for (String pathname : pathnames) {
              // Print the names of files and directories
              sb.append("[File=" +pathname+"]");

          }


        return Uni.createFrom().item(sb.toString())
                .map(res -> FileList.newBuilder().setList(res).build());
    }

}

Generating the Classes from Proto files

To generate the Classes and interfaces defined in the Proto file you include the plugin quarkus-maven-plugin with the goals generate-code and generate-code-tests:

<plugin>
    <groupId>${quarkus.platform.group-id}</groupId>
    <artifactId>quarkus-maven-plugin</artifactId>
    <version>${quarkus.platform.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>build</goal>
                <goal>generate-code</goal>
                <goal>generate-code-tests</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Then, when you build the project:

mvn install

the Java classes will be generated under the folder target/generated-sources/grpc:

target/generated-sources/grpc
└── com
    └── mastertheboss
        └── filesystem
            ├── Directory.java
            ├── DirectoryOrBuilder.java
            ├── FileList.java
            ├── FileListOrBuilder.java
            ├── FileManagerBean.java
            ├── FileManagerClient.java
            ├── FileManagerGrpc.java
            ├── FileManager.java
            ├── Helloworld.java
            └── MutinyFileManagerGrpc.java

Testing the application

You can use the development profile to test the application

mvn quarkus:dev

As you can see from the Console log, the gRPC Server started on 0.0.0.0:9000:

If needed, you can change the default port (9000) with the following configuration property:

quarkus.grpc.clients."client-name".port

We can test the gRPC Endpoints as follows:

$ curl localhost:8080/filesystem/blocking?dir=/tmp

$ curl localhost:8080/filesystem/mutiny?dir=/tmp

In both cases, you should be able to see the directory listing for the server path “/tmp”.

You can also test the application through the native gRPC port. This requires insalling the grpcurl tool from https://github.com/fullstorydev/grpcurl/releases

Once installed, you can invoke the remote service as follows:

grpcurl --plaintext -d '{"name": "/tmp"}' localhost:9000 filesystem.FileManager.ReadDir

Conclusion

This article showed how to run a simple gRPC Service that we have designed in Java as Quarkus REST Service.
You can find the source code for this article here: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/grpc-demo

Found the article helpful? if so please follow us on Socials