gRPC MicroProfile Server Services

The gRPC Microprofile APIs are an extension to Helidon MP to allow building of gRPC services and clients that integrate with the Microprofile APIs. Using Helidon gRPC MP makes building gRPC services and clients an easier process that the traditional approach using Protobuf files and code generation. Services can be built using POJOs that are then discovered and deployed at runtime in the same way the Helidon MP discovers and deploys web resources in the MP http server.

Building gRPC services using Helidon gRPC MP is very simple and allows the developer to concentrate on their application logic without needing to write a lot of boilerplate gRPC code.

Maven Coordinates

To enable gRPC MicroProfile Server add the following dependency to your project’s pom.xml (see Managing Dependencies).

<dependency>
    <groupId>io.helidon.microprofile.grpc</groupId>
    <artifactId>helidon-microprofile-grpc-server</artifactId>
</dependency>
Copied

Defining a Service

The traditional approach to building Java gRPC services is to write Protobuf files describing the service and then use these to generate service stubs and finally implementing the service methods by extending the generated stub classes. Using Helidon gRPC MP you just need to write an annotated service implementation class that is just a normal POJO.

For example:

Simple gRPC Service
@ApplicationScoped
@io.helidon.microprofile.grpc.core.Grpc
public class StringService {

    @io.helidon.microprofile.grpc.core.Unary
    public String upper(String s) {
        return s == null ? null : s.toUpperCase();
    }
}
Copied

The code above is a simple service with a single unary method that just converts a String to uppercase. The important parts in the example are the @ApplicationScoped, @Grpc and @Unary annotations; these, along with other annotations discussed later, allow the gRPC MP APIs to discover, configure and deploy the service.

Of course Helidon gRPC MP does not preclude you from using the Protobuf files approach, traditional gRPC Java services also work in a gRPC MP server.

As already shown above a Helidon gRPC MP service is just an annotated POJO. To make a class a service it requires two annotations.

@ApplicationScoped                                
@io.helidon.microprofile.grpc.core.Grpc     
public class StringService {
Copied
  • The ApplicationScoped annotation is what makes the service implementation a CDI bean and hence discoverable.
  • The Grpc annotation is what defines the class as a gRPC service so that when the bean is discovered it is then deployed by the gRPC MP server.

Service Name

By default when a class is annotated with Grpc the class name will be used as the gRPC service name. So in the example above the service name will be StringService. This can be change by supplying a name to the annotation.

@ApplicationScoped
@io.helidon.microprofile.grpc.core.Grpc(name="Strings") 
public class StringService {
Copied
  • in the example above the name of the deployed service will be Strings.

Defining Service Methods

Once a class is properly annotated to make it a gRPC MP service it needs to have service methods that implement the application business logic. In gRPC there are four different types of method:

  • Unary - a simple method with at most a single request value and returning at most a single response value.

  • Server Streaming - a method that takes at most a single request value but may return zero or more response values.

  • Client Streaming - a request that takes one or more request values and returns at most one response value.

  • Bi-directional Streaming - a method that can take one or more request values and return zero or more response values.

The Helidon gRPC MP API determines a method type by its annotation, which should be one of the following:

@io.helidon.microprofile.grpc.core.Unary
@io.helidon.microprofile.grpc.core.ServerStreaming
@io.helidon.microprofile.grpc.core.ClientStreaming
@io.helidon.microprofile.grpc.core.Bidirectional
Copied

Request an Response Types

A gRPC service method typically takes a request parameter and returns a response value (streaming methods may take or return multiple requests or responses). In traditional gRPC Java the types used for the request and response values must be Protobuf serializable classes but this is not the case with Helidon gRPC. Helidon supports pluggable Marshallers and by default will support any Java primitive or Java Serializable as well as Protobuf types. Any type that can be marshalled by the built-in marshallers or custom supplied marshaller may be used as a request or response type.

Unary Methods

A unary gRPC method is the simplest type of service method. Typically a unary method takes a request value and returns a response value but this does not have to be the case, a unary method could just as easily take no request parameter and/or return no response.

All of the signatures below are valid unary methods in Helidon gRPC MP.

// A unary method with a simple request and response
@io.helidon.microprofile.grpc.core.Unary
public ResponseType invoke(RequestType req)

// A unary method that just returns a response
@io.helidon.microprofile.grpc.core.Unary
public ResponseType invoke()

// A unary method that takes a request but returns no response
@io.helidon.microprofile.grpc.core.Unary
public void invoke(RequestType req)

// A unary method that takes no request and returns no response
@io.helidon.microprofile.grpc.core.Unary
public void invoke()

// An async unary request that takes a request and returns a future
// that will complete when the response is ready
@io.helidon.microprofile.grpc.core.Unary
public CompletableFuture<ResponseType> invoke(RequestType req)

// An async unary request that takes no request and returns a future
// that will complete when the response is ready
@io.helidon.microprofile.grpc.core.Unary
public CompletableFuture<ResponseType> invoke()

// The standard gRPC Java unary method signature
@io.helidon.microprofile.grpc.core.Unary
public void invoke(RequestType req, StreamObserver<ResponseType> observer)

// The standard gRPC Java unary method signature but without a request type
@io.helidon.microprofile.grpc.core.Unary
public void invoke(StreamObserver<ResponseType> observer)

// A unary method that takes a request type and a future to complete
// with the response type
@io.helidon.microprofile.grpc.core.Unary
public void invoke(RequestType req, CompletableFuture<ResponseType> observer)

// A unary method that takes no request type but just takes a future
// to complete with the response type
@io.helidon.microprofile.grpc.core.Unary
public void invoke(CompletableFuture<ResponseType> observer)
Copied

The various signatures supported above allow the service developer to choose the method signature that best fits their application business logic without needing to worry about handling standard gRPC Java requests and StreamObservers. The standard gRPC Java method signature is in the list above so it can still be used if required.

ServerStreaming Methods

A server streaming method receives a requests from the client and when the request stream is complete it sends back a stream of response values. A traditional gRPC Java server streaming method takes two parameters, the request and a StreamObserver that is used to send back the single response in the same way that a unary method sends a response. As with unary methods Helidon gRPC MP supports different method signatures for server streaming methods.

All of the signatures below are valid server streaming methods in Helidon gRPC MP.

// The standard gRPC Java server streaming method signature
@io.helidon.microprofile.grpc.core.ServerStreaming
public void invoke(RequestType req, StreamObserver<ResponseType> observer)

// A server streaming method that uses a Stream to send the responses to the client
@io.helidon.microprofile.grpc.core.ServerStreaming
public Stream<ResponseType> invoke(RequestType req)

// The server streaming method without a request parameter
@io.helidon.microprofile.grpc.core.ServerStreaming
public void invoke(StreamObserver<ResponseType> observer)

// A server streaming method without a request parameter
// that uses a Stream to send the responses to the client
@io.helidon.microprofile.grpc.core.ServerStreaming
public Stream<ResponseType> invoke(RequestType req)
Copied

As with unary methods, the Helidon gRPC MP API supports multiple different method signatures for implementing server streaming methods.

ClientStreaming Methods

A client streaming method receives a stream of requests from the client and when the request stream is complete it sends back a response. A traditional gRPC Java client streaming method takes two StreamObserver parameters, one is the stream of client requests and the other is used to send back the single response in the same way that a unary method sends a response. As with unary methods Helidon gRPC MP supports different method signatures for client streaming methods.

All of the signatures below are valid client streaming methods in Helidon gRPC MP.

// The standard gRPC Java client streaming method signature
@io.helidon.microprofile.grpc.core.ClientStreaming
public StreamObserver<RequestType> invoke(StreamObserver<ResponseType> observer)

// The gRPC Java client streaming method with an asynchronous response
@io.helidon.microprofile.grpc.core.ClientStreaming
public StreamObserver<RequestType> invoke(CompletableFuture<ResponseType> observer)
Copied

Bi-Directional Streaming Methods

A bidirectional streaming method is a method that is a constant stream of client requests and server responses. Other than the standard gRPC Java StreamObserver there are not any other built in types that make sense to use to implement different method signatures for a bidirectional method so the only supported signature is the standard gRPC Java method.

@io.helidon.microprofile.grpc.core.Bidirectional
public StreamObserver<RequestType> invoke(StreamObserver<ResponseType> observer)
Copied

Deploying Protobuf Services

Whilst the examples above show how simple it is to write gRPC services with basic POJOs there may be cases where there is a requirement to deploy services built the traditional way using gRPC Java generated classes or built as non-microprofile Helidon gRCP services.

Annotate the Service Implementation

When the gRPC MP server is starting it will discover all CDI beans of type io.grpc.BindableService. Service sub-classes implemented the traditional way with code generation are instances of BindableService so by annotating the implementation class with the @ApplicationScoped annotation they become discoverable and will be deployed into the gRPC server.

@ApplicationScoped
public class StringService
    extends StringServiceGrpc.StringServiceImplBase {
Copied

In exactly the same way, if a class is an implementation of io.helidon.grpc.server.GrpcService then by annotating the class with the @ApplicationScoped annotation it will be discovered and deployed when the MP gRPC server starts.

@ApplicationScoped
public class StringService implements GrpcService {
Copied

Implement a GrpcMpExtension

If it is not possible to annotate the service class (for example the code is built by a third party) another way to deploy none CDI bean services is to implement a gRPC MP server extension. The extension will then be called when the MP server is starting and be given the chance to add additional services for deployment. An extension should implement the io.helidon.microprofile.grpc.server.spi.GrpcMpExtension interface.

For example, assuming that there was a gRPC service class called StringService that needed to be deployed an extension class might look like this:

public class MyExtension
        implements GrpcMpExtension {
    @Override
    public void configure(GrpcMpContext context) {  
        context.routing()
               .register(new ServiceService());     
    }
}
Copied
  • The configure method of the extension will be called to allow the extension to add extra configuration to the server.
  • In this example an instance of the StringService is registered with the routing (as described in the basic gRPC server documentation).

The GrpcMpExtension instances are discovered and loaded using the service loader so for the example above to work a file META-INF/services/io.helidon.microprofile.grpc.server.spi.GrpcMpExtension would need to be created that contained the names of the service implementations.