Implementing a gRPC Service

gRPC service implementations exposed as beans are automatically registered and served by quarkus-grpc.

Implementing a gRPC service requires the gRPC classes to be generated. Place your proto files in src/main/proto and run mvn compile.

Implementation base

The generation has created 2 implementation bases:

  1. One using the default gRPC API

  2. One using the Mutiny API

The first classname is structured as follows: ${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase. The second classname is structured as follows: Mutiny${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase.

For example, if you use Greeter as service name as in:

  1. service Greeter {
  2. rpc SayHello (HelloRequest) returns (HelloReply) {}
  3. }

The regular implementation base is: GreeterGrpc.GreeterImplBase. The second implementation base is: MutinyGreeterGrpc.GreeterImplBase.

Note that these classes are not interfaces but regular classes. When extending them, you need to override the service methods defined in the service definition.

Implementing a service with the default gRPC API

To implement a gRPC service using the default gRPC API, create a class extending the default implementation base. Then, override the methods defined in the service interface. Finally, expose the service as a CDI bean using the @Singleton annotation:

  1. import javax.inject.Singleton;
  2. @Singleton
  3. public class HelloService extends GreeterGrpc.GreeterImplBase {
  4. @Override
  5. public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
  6. String name = request.getName();
  7. String message = "Hello " + name;
  8. responseObserver.onNext(HelloReply.newBuilder().setMessage(message).build());
  9. responseObserver.onCompleted();
  10. }
  11. }

Implementing a service with the Mutiny API

To implement a gRPC service using the Mutiny gRPC API, create a class extending the Mutiny implementation base. Then, override the methods defined in the service interface. These methods are using Mutiny types. Finally, expose the service as a CDI bean using the @Singleton annotation:

  1. import javax.inject.Singleton;
  2. @Singleton
  3. public class ReactiveHelloService extends MutinyGreeterGrpc.GreeterImplBase {
  4. @Override
  5. public Uni<HelloReply> sayHello(HelloRequest request) {
  6. return Uni.createFrom().item(() ->
  7. HelloReply.newBuilder().setMessage("Hello " + request.getName()).build()
  8. );
  9. }
  10. }

Handling streams

gRPC allows receiving and returning streams:

  1. service Streaming {
  2. rpc Source(Empty) returns (stream Item) {} // Returns a stream
  3. rpc Sink(stream Item) returns (Empty) {} // Reads a stream
  4. rpc Pipe(stream Item) returns (stream Item) {} // Reads a streams and return a streams
  5. }

Using Mutiny, you can implement these as follows:

  1. @Singleton
  2. public class StreamingService extends MutinyStreamingGrpc.StreamingImplBase {
  3. @Override
  4. public Multi<Item> source(Empty request) {
  5. // Just returns a stream emitting an item every 2ms and stopping after 10 items.
  6. return Multi.createFrom().ticks().every(Duration.ofMillis(2))
  7. .transform().byTakingFirstItems(10)
  8. .map(l -> Item.newBuilder().setValue(Long.toString(l)).build());
  9. }
  10. @Override
  11. public Uni<Empty> sink(Multi<Item> request) {
  12. // Reads the incoming streams, consume all the items.
  13. return request
  14. .map(Item::getValue)
  15. .map(Long::parseLong)
  16. .collectItems().last()
  17. .map(l -> Empty.newBuilder().build());
  18. }
  19. @Override
  20. public Multi<Item> pipe(Multi<Item> request) {
  21. // Reads the incoming stream, compute a sum and return the cumulative results
  22. // in the outbound stream.
  23. return request
  24. .map(Item::getValue)
  25. .map(Long::parseLong)
  26. .onItem().scan(() -> 0L, Long::sum)
  27. .onItem().transform(l -> Item.newBuilder().setValue(Long.toString(l)).build());
  28. }
  29. }

Health check

For the exposed services, Quarkus gRPC exposes health information in the following format:

  1. syntax = "proto3";
  2. package grpc.health.v1;
  3. message HealthCheckRequest {
  4. string service = 1;
  5. }
  6. message HealthCheckResponse {
  7. enum ServingStatus {
  8. UNKNOWN = 0;
  9. SERVING = 1;
  10. NOT_SERVING = 2;
  11. }
  12. ServingStatus status = 1;
  13. }
  14. service Health {
  15. rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
  16. rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
  17. }

Clients can specify the fully qualified service name to get the health status of a specific service or skip specifying the service name to get the general status of the gRPC server.

For more details, check out the gRPC documentation

Additionally, if Quarkus SmallRye Health is added to the application, a readiness check for the state of the gRPC services will be added to the MicroProfile Health endpoint response, that is /health.

Reflection Service

Quarkus gRPC Server implements the reflection service. This service allows tools like grpcurl or grpcox to interact with your services.

The reflection service is enabled by default in dev mode. In test or production mode, you need to enable it explicitly by setting quarkus.grpc.server.enable-reflection-service to true.

Scaling

By default, quarkus-grpc starts a single gRPC server running on a single event loop.

If you wish to scale your server, you can set the number of server instances by setting quarkus.grpc.server.instances.

Server configuration

About the Duration format

The format for durations uses the standard java.time.Duration format. You can learn more about it in the Duration#parse() javadoc.

You can also provide duration values starting with a number. In this case, if the value consists only of a number, the converter treats the value as seconds. Otherwise, PT is implicitly prepended to the value to obtain a standard java.time.Duration format.

Example of configuration

Enabling TLS

To enable TLS, use the following configuration:

  1. quarkus.grpc.server.ssl.certificate=src/main/resources/tls/server.pem
  2. quarkus.grpc.server.ssl.key=src/main/resources/tls/server.key
When SSL/TLS is configured, plain-text is automatically disabled.

TLS with Mutual Auth

To use TLS with mutual authentication, use the following configuration:

  1. quarkus.grpc.server.ssl.certificate=src/main/resources/tls/server.pem
  2. quarkus.grpc.server.ssl.key=src/main/resources/tls/server.key
  3. quarkus.grpc.server.ssl.trust-store=src/main/resources/tls/ca.jks
  4. quarkus.grpc.server.ssl.trust-store-password=*****
  5. quarkus.grpc.server.ssl.client-auth=REQUIRED