Service Implementation
While Helidon gRPC Server allows you to deploy any standard gRPC service that implements io.grpc.BindableService interface, including services generated from the Protobuf IDL files (and even allows you to customize them to a certain extent), using Helidon gRPC framework to implement your services has a number of benefits:
It allows you to define both HTTP and gRPC services using similar programming model, simplifying learning curve for developers.
It provides a number of helper methods that make service implementation significantly simpler.
It allows you to configure some of the Helidon value-added features, such as security and metrics collection down to the method level.
It allows you to easily specify custom marshaller for requests and responses if Protobuf does not satisfy your needs.
It provides built in support for health checks.
Service Implementation Basics
At the very basic level, all you need to do in order to implement a Helidon gRPC service is create a class that implements io.helidon.grpc.server.GrpcService interface and define one or more methods for the service:
class EchoService implements GrpcService {
@Override
public void update(ServiceDescriptor.Rules rules) {
rules.unary("Echo", this::echo);
}
/**
* Echo the message back to the caller.
*
* @param request the echo request containing the message to echo
* @param observer the response observer
*/
public void echo(String request, StreamObserver<String> observer) {
complete(observer, request);
}
}- Define unary method
Echoand map it to thethis::echohandler. - Create a handler for the
Echomethod. - Send the request string back to the client by completing response observer.
The complete method shown in the example above is just one of many helper methods available in the GrpcService class. See the full list here.
The example above implements a service with a single unary method, which will be exposed at the `EchoService/Echo' endpoint. The service does not explicitly define a marshaller for requests and responses, so Java serialization will be used as a default.
Unfortunately, this implies that you will have to implement clients by hand and configure them to use the same marshaller as the server. Obviously, one of the major selling points of gRPC is that it makes it easy to generate clients for a number of languages (as long as you use Protobuf for marshalling), so let’s see how we would implement Protobuf enabled Helidon gRPC service.
Implementing Protobuf Services
In order to implement Protobuf-based service, you would follow the official instructions on the gRPC web site, which boil down to the following:
Define the Service IDL
For this example, we will re-implement the EchoService above as a Protobuf service in echo.proto file.
syntax = "proto3";
option java_package = "org.example.services.echo";
service EchoService {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}Based on this IDL, the gRPC compiler will generate message classes (EchoRequest and EchoResponse), client stubs that can be used to make RPC calls to the server, as well as the base class for the server-side service implementation.
We can ignore the last one, and implement the service using Helidon gRPC framework instead.
Implement the Service
The service implementation will be very similar to our original implementation:
class EchoService implements GrpcService {
@Override
public void update(ServiceDescriptor.Rules rules) {
rules.proto(Echo.getDescriptor())
.unary("Echo", this::echo);
}
/**
* Echo the message back to the caller.
*
* @param request the echo request containing the message to echo
* @param observer the response observer
*/
public void echo(Echo.EchoRequest request, StreamObserver<Echo.EchoResponse> observer) {
String message = request.getMessage();
Echo.EchoResponse response = Echo.EchoResponse.newBuilder().setMessage(message).build();
complete(observer, response);
}
}- Specify proto descriptor in order to provide necessary type information and enable Protobuf marshalling.
- Define unary method
Echoand map it to thethis::echohandler. - Create a handler for the
Echomethod, using Protobuf message types for request and response. - Extract message string from the request.
- Create the response containing extracted message.
- Send the response back to the client by completing response observer.