gRPC MicroProfile Clients

Building Java gRPC clients using the Helidon MP gRPC APIs is very simple and removes a lot of the boiler plate code typically associated to more traditional approaches to writing gRPC Java clients. At it simplest a gRPC Java client can be written using nothing more than a suitably annotated interface.

Maven Coordinates

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

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

Building a gRPC Client

There are a few steps to building and using a gRPC client in Helidon MP.

As discussed in the section on Server-Side Services there are four different types of gRPC 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.

An as with the server-side APIS, the Helidon MP gRPC client APIs support a number of different method signatures for each of the different gRPC method types.

The Client Service Interface

The next step is to produce an interface with the service methods that the client requires.

For example, suppose we have a simple server side service that has a unary method to convert a string to uppercase.

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

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

The service has been written using the Helidon MP APIs but could just as easily be a traditional gRPC Java service generated from Protobuf files. The client API is agnostic of the server side implementation, it only cares about the method type, the request and response types and the type of Marshaller used to serialize the request and response.

To write a client for the StringService all that is required is an interface.

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

    @io.helidon.microprofile.grpc.core.Unary
    public String upper(String s);
}
Copied

There is no need to write any code to implement the client. The Helidon MP gRPC APIs will create a dynamic proxy for the interface using the information from the annotations and method signatures.

The interface in the example above used the same method signature as the server but this does not have to be the case, the interface could have used any supported signature for a unary method, so for example it could just have easily been the standard unary method signature:

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

    @io.helidon.microprofile.grpc.core.Unary
    public void upper(String s, StreamObserver<String> response);
}
Copied

We could also have made the client asynchronous by using one of the async method signatures:

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

    @io.helidon.microprofile.grpc.core.Unary
    public CompletableFuture<String> upper(String s);
}
Copied

Configuring Channels

For a gRPC client to connect to a server it requires a Channel. The Helidon MP gRPC APIs provide a way to inject channels into CDI beans that require them.

Channels are configured in the grpc section of the Helidon application configuration. The examples below use an application.yaml file but there are many other ways to use and override configuration in Helidon

application.yaml
grpc:
  channels:                
    test-server:           
      host: localhost      
      port: 1408           
Copied
  • Channels are configured in the`channels` section
  • Each sub-section is the Channel name that is then used to refer to this Channel in the application code
  • Each channel contains a host name
  • and a port.

While most client application only connect to a single server it is possible to configure multiple named channels if the client needs to connect to multiple servers.

application.yaml
grpc:
  channels:
    london:
      host: london.foo.com
      port: 1408
    new-york:
      host: ny.foo.com
      port: 1408
Copied

The above example shows two channel configurations, one named london and the other new-york.

Configuring TLS

It is also possible to configure a Channel to use TLS if the server is using TLS.

application.yaml
grpc:
  channels:
    test-server:
      host: localhost
      port: 1408
      tls:                          
        enabled: true               
        tls-cert-path: /certs/foo.cert    
        tls-key-path: /certs/foo.key      
        tls-ca-cert-path: /certs/ca.cert   
Copied
  • The tls section of the channel configuration is used to configure TLS.
  • The enabled value is used to enable or disable TLS for this channel.
  • The tls-cert value is the location of the TLS certificate file
  • The tls-key value is the location of the TLS key file
  • The tls-ca-cert value is the location of the TLS CA certificate file

The SSL configuration uses the Helidon Resource class to locate configured keys and certificates. In the example above the tls-cert-path config key has the -path suffix which tells the configuration to load /certs/foo.cert as a file. If /certs/foo.cert was a resource on the classpath the configuration key could have been changed to tls-cert-resource-path to load /certs/foo.cert from the classpath. The same applies to the tls-key and tls-ca-cert configuration keys. See the io.helidon.common.configurable.Resource class for details.

Using Channels

Once one or more channels have been configured they can be used by client code. The simplest way to use a channel is to inject it into beans using CDI. The Helidon gRPC client APIs have CDI producers that can provide io.grpc.Channel instances.

For example, a class might have an injectable io.grpc.Channel field:

gRPC Channel Injection
    @Inject                             
    @GrpcChannel(name = "test-server")  
    private Channel channel;
Copied
  • The @Inject annotation tells CDI to inject the channel.
  • The @GrpcChannel annotation is the qualifier that supplies the Channel name. This is the same name as used in the channel configuration in the configuration examples above.

When an instance of the CDI bean with the channel field is instantiated a channel will be injected into it.

The In-Process Channel

If code is running in an application that is executing as part of a Helidon MP gRPC server there is a special in-process channel available. This allows code executing on the server to make calls to gRPC services deployed on that server in the same way an external client does. To inject an in-process channel a different qualifier annotation is used.

gRPC in-Process Channel Injection
    @Inject                  
    @InProcessGrpcChannel    
    private Channel channel;
Copied
  • The @Inject annotation is used the same as previously.
  • The @InProcessGrpcChannel is the qualifier that is used to tell the Helidon MP gRPC API to inject an in-process channel.

Using the Client Interface in an Application

Now that there is a client interface and a Channel configuration we can use these in the client application. The simplest way is to use the client in a CDI microprofile application.

In the application class that requires the client we can declare a field of the same type as the client service interface. The field is then annotated so that CDI will inject the client proxy into the field.

Simple gRPC Service
@ApplicationScoped
public class Client {

    @Inject                                  
    @GrpcProxy                        
    @GrpcChannel(name = "test-server")       
    private StringService stringService;
Copied
  • The @Inject annotation tells the CDI to inject the client implementation; the gRPC MP APIs have a bean provider that does this.
  • The @GrpcProxy annotation is used by the CDI container to match the injection point to the gRPC MP APIs provider
  • The @GrpcChannel annotation identifies the gRPC channel to be used by the client. The name used in the annotation refers to a channel name in the application configuration.

Now when the CDI container instantiates instances of the Client it will inject a dynamic proxy into the stringService field and then any code in methods in the Client class can call methods on the StringService which will be translated to gRPC calls.

In the example above there is no need to directly use a Channel directly. The correct channel is added to the dynamic client proxy internally by the Helidon MP gRPC APIs.