Contents
Overview
The gRPC MicroProfile (MP) API is an extension to Helidon MP that enables building gRPC services integrated with MicroProfile APIs. Using Helidon MP simplifies the process of developing gRPC services compared to traditional approaches. Services can be implemented as plain POJOs, which are then automatically discovered and deployed at runtime—just like other Helidon MP web services.
Building gRPC services with Helidon gRPC MP is straightforward, allowing developers to focus on application logic rather than boilerplate 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>
Additional dependencies may be required depending on your application needs. See the gRPC MP Example for a complete example.
API
All Helidon gRPC MP annotations are defined in the Grpc interface. The following annotations are
used to implement Helidon MP gRPC services:
-
@Grpc.GrpcService: an annotation that marks a class as a gRPC service. -
@Grpc.GrpcMarshaller: an annotation on a type or method that specifies a named marshaller supplier. This annotation is required when not using Protobuf serialization. -
@Grpc.Proto: an annotation for an optional method returning the Protobuf descriptor. For more information see the gRPC Reflection Service.
The following gRPC method types are supported:
-
@Grpc.Unary: a method that takes a single value (or void) and returns a single value (or void). -
@Grpc.ServerStreaming: a method that takes a single value (or void) and returns a stream of values. -
@Grpc.ClientStreaming: a method that takes a stream of values and returns a single value (or void). -
@Grpc.Bidirectional: a method that takes a stream of values and returns a stream of values.
Usage
Defining a Service
The traditional approach to building Java gRPC services is to write Protobuf files describing a service, use these files to generate service stubs, and then implement the service methods from stub classes. In Helidon MP, you have a simpler option of just writing normal POJOs that refer to the Protobuf message types without the need to extend any generated stubs.
For example:
@ApplicationScoped
@Grpc.GrpcService
public class StringService {
@Grpc.Unary("Upper")
public Strings.StringMessage upper(Strings.StringMessage request) {
String text = request.getText().toUpperCase();
return Strings.StringMessage.newBuilder().setText(text).build();
}
}
Note that the message types in Strings are generated from a Protobuf file, but the class
itself is just a POJO that uses the Helidon MP annotations described above. In addition,
@Grpc.Unary overrides the Java method name to match that in the Protobuf file,
as shown next:
syntax = "proto3";
service StringService {
rpc Upper (StringMessage) returns (StringMessage) {}
}
message StringMessage {
string text = 1;
}
When using Maven, Protobuf files should be placed under the src/main/proto directory.
It’s recommended to use the protobuf-maven-plugin to compile these files as part of
the Maven build process. You can refer to the
pom.xml
file in the Helidon gRPC MP example for guidance.
Using Custom Marshalers
Even though it is recommended to use Protobuf message types, it is not mandatory. Traditional
Java types can be used as long as custom marshalers are provided. For instance, in the example
above we can use a String type instead of the generated type StringMessage, if we create
a marshaler for it. For example,
@ApplicationScoped
@Grpc.GrpcService
@Grpc.GrpcMarshaller("string")
public class StringService {
@Grpc.Unary("Upper")
public String upper(String request) {
return request.toLowerCase();
}
}
In this example, the marshaler is provided using the name "string" and its supplier must
be discoverable via CDI. The following is an example of a marshaler and its supplier for the
String type:
@Dependent
@Named("string")
public class StringSupplier implements MarshallerSupplier {
@Override
public <T> MethodDescriptor.Marshaller<T> get(Class<T> clazz) {
return new StringMarshaller<>();
}
}
public class StringMarshaller<String>
implements MethodDescriptor.Marshaller<String> {
@Override
public InputStream stream(String obj) {
InputStream stream = null;
// convert to stream
return stream;
}
@Override
@SuppressWarnings("unchecked")
public String parse(InputStream in) {
String string = null;
// parse from stream
return string;
}
}
Annotating the supplier with @Dependent ensures discoverability provided CDI is configured
to find all annotated beans in the corresponding beans.xml file.
Implementing a gRPC Extension
When unable to annotate a service class —for example when the code is built by a third party—
another way to deploy non-CDI bean services is to implement a gRPC MP server extension.
Such an extension will be called when the MP server is starting and be given the chance to provide
additional services for deployment.
An extension must implement the io.helidon.microprofile.grpc.server.spi.GrpcMpExtension interface.
For example, assuming that there is a gRPC service class called StringService that needs to
be deployed, a gRPC server extension class might look like this:
public class MyExtension implements GrpcMpExtension {
@Override
public void configure(GrpcMpContext context) { // (1)
context.routing().service(new StringService()); // (2)
}
}
-
The
configuremethod of the extension will be called to allow the extension to add extra configuration to the server. -
In this example, an instance of the
StringServiceis registered with the routing, as described in the gRPC Server Routing documentation.
The GrpcMpExtension instances are discovered and loaded using the service loader, so for
this example above to work, a file
META-INF/services/io.helidon.microprofile.grpc.server.spi.GrpcMpExtension
would need to be created with the name of the extension shown above.
gRPC Reflection Service
When a gRPC client interacts with a server, it needs to have access to the
Protobuf file to learn about the available services, methods and
message types in use. For many applications, this information is simply
common knowledge between the two parties. However, in some cases, especially
while developing a new service, it is convenient to use tools such as grpcurl
or Postman to test a service.
Helidon includes a gRPC reflection service that can be queried by client tools to learn about the available services —similar to OpenAPI for REST services. The reflection service is implemented as a feature and can be enabled programmatically by adding the feature, or via config as follows:
features:
grpc-reflection:
enabled: true
For more information see gRPC Server Configuration.
In Helidon MP, annotated services must provide access to the underlying
Protobuf description to use the reflection service. Here is a modified
version of StringService that adds an annotated method returning the
descriptor:
@ApplicationScoped
@Grpc.GrpcService
public class StringService {
@Grpc.Proto
public Descriptors.FileDescriptor proto() {
return Strings.getDescriptor();
}
@Grpc.Unary("Upper")
public Strings.StringMessage upper(Strings.StringMessage request) {
String text = request.getText().toUpperCase();
return Strings.StringMessage.newBuilder().setText(text).build();
}
}
A method annotated by @Grpc.Proto must return type Descriptors.FileDescriptor;
such a descriptor is available from the Protobuf generated code, in this example,
the Strings.getDescription() method. The reflection service shall call this
method to provide service introspection to any clients that support the protocol.
Configuration
At the time of writing, there is no configuration that is specific to Helidon MP. For more information about gRPC configuration in SE, see gRPC Server Configuration.
Examples
Please refer to the Helidon gRPC MP Example.