EXPEDIA GROUP TECHNOLOGY — SOFTWARE

Introducing gRPC to our Hotels.com Platform — Part 2

Continuing our learning about gRPC — this time implementing a service

Nikos Katirtzis
Expedia Group Technology
7 min readMar 5, 2020

--

Introducing gRPC to our Hotels.com Platform

In the first part of our series we introduced gRPC focusing on services written in Java and deployed on Kubernetes. In this blog we’ll present a simple Spring Boot application which exposes a gRPC endpoint and we’ll go through the code you need on both the server side and the client side. Although this application is far from a production-ready service it can be used as a starting point. Within Hotels.com™ (part of Expedia Group™) we use both gRPC and REST/HTTP services, though gRPC is only used internally

Application overview

The project can be found here. It is a Spring Boot application written in Java and is built using Maven. It integrates with Micrometer for metrics and has been instrumented to report traces to Zipkin. To avoid creating 2 projects, one for the server side and one for the client side we have implemented the client side in the form of an integration test.

gRPC Java libraries

We’ll start with integrating the libraries to the project. As you see in Figure 1, we add a couple of gRPC related libraries which are part of the Java gRPC implementation. We also include the brave-instrumentation-grpc library for tracing gRPC requests. In addition to those libraries and as seen in Figure 2, we need the Maven Protocol Buffers Plugin to generate Java source files from our .proto file(s). The integration with the plugin is described here.

Figure 1: gRPC dependencies in the pom.xml file.
Figure 2: The Maven Protocol Buffers plugin is used to generate Java source files from our .proto file.

Proto file and codegen

Next we’ll define a basic call. As shown in Figure 3, our proto file includes a simple/unary RPC where the client requests a review using its id from the server and waits for a response.

Figure 3: Example of a proto file with a unary RPC.

When compiling the project a class named ReviewsServiceGrpc is generated. This class includes the abstract ReviewsServiceImplBase class which one can extend to provide a custom implementation. For an override example, in Figure 4 we override the getReviews method to return a dynamic output based on the input.

Figure 4: Overriding the auto-generated method to return the desired output.

Starting the gRPC Server

In order for our application to serve requests over gRPC we need to start the gRPC server. This is done in the GrpcConfiguration class which could be summarised to the code shown in Figure 5.

Figure 5: Snippet that starts the gRPC server.

Note that we’ve enabled server reflection so we can call the application without precompiled information.

At this point we have a basic version of the service and we should be able to call the Reviews service as illustrated in Figure 6.

Figure 6: Spring Boot application running a gRPC server.

gRPC Health check

The official gRPC repository defines the gRPC Health Checking Protocol which is already implemented in Java (see here and here). To enable it you can use the HealthStatusManager provided by the grpc-services library as shown in Figure 7.

Figure 7: Adding the Health Service and setting its status to SERVING.

If you deploy your application on Kubernetes and you want to use the gRPC health check for liveness and readiness probes check this blog.

gRPC interceptors

It’s pretty common to use interceptors either on the server-side or on the client-side for use cases such as monitoring and logging.

The gRPC Java implementation provides an interface for both the server side and the client side. As an example, we’ve implemented a server-side interceptor which provides access logs (Figure 8), and another one which translates and augments gRPC exceptions (GrpcExceptionInterceptor). In addition to the custom interceptors we implement, we add one for tracing which is already implemented in the brave-instrumentation-grpc library.

Figure 8: Implementation of a -gRPC- ServerInterceptor that provides access log functionality.

The final step is to add the interceptors to the server as shown in Figure 9.

Figure 9: Adding the interceptors to the server.

Calling the service using cmd tools

As mentioned in the first blog, there are many command line tools for making gRPC requests.

The default tool is called grpc_cli and an example of listing all the services exposed by the application over port 6565 can be seen in Figure 10.

Figure 10: Listing services exposed by the gRPC server using the grpc_cli tool.

As shown above, the application exposes the ReviewsService, another service which provides reflection functionality and one for health checks. You can even list details for a particular service as depicted in Figure 11.

Figure 11: Listing service details using the grpc_cli tool.

To call the main endpoint you can use the call method (Figure 12).

Figure 12: Call the RPC using the grpc_cli tool.

Similarly, you can use the grpcurl tool to obtain the same information as shown in Figure 13.

Figure 13: Call the RPC using the grpcurl tool.

The syntax for checking the server’s health status is very similar. In that case you need to call the Check method (Figure 14).

Figure 14: Get the gRPC server’s health status using the grpc_cli tool.

Implementing the client side

As mentioned earlier, we implement the client side using an integration test (ReviewsTestIT). In that class and for demonstration purposes we include 2 tests; the first one (syncClientShouldReturnReview) is using a blocking/synchronous stub and the second one (asyncClientShouldReturnReview) uses a future stub.

As one might notice, the GrpcSyncClient (Figure 15) and the GrpcAsyncClient (Figure 16) share a lot of common code:

  • They use a plaintext (non-TLS) connection to the server. This is done by calling the useplaintext method.
  • They include 2 interceptors, one for tracing, and a custom one for capturing metrics which is presented in Figure 17.
  • A deadline is set by calling the withDeadlineAfter method.
  • Request headers are attached to the stub by first constructing the metadata object and then calling the attachHeaders of the MetadataUtils class.
  • Any StatusRuntimeExceptions are caught and appropriate status codes are returned.
  • The response is an object which includes the status code and the body of the response.
  • The channel is shut down once the RPC call succeeds. Note that in a real world scenario you wouldn’t want to only open the channel for a single request.
Figure 15: Synchronous client using a blocking stub for RPC calls.
Figure 16: Asynchronous client using a future stub for RPC calls.
Figure 17: Implementation of a -gRPC- ClientInterceptor that captures gRPC metrics.

That said, as shown above, the main differences between the 2 clients is that the synchronous one is calling the RPC method using an object of type ReviewsServiceGrpc.ReviewsServiceBlockingStub while the asynchronous one uses an object of type ReviewsServiceGrpc.ReviewsServiceFutureStub.

Also, it should be clear from Figure 18 and Figure 19 that the type of the body in the response is slightly different since the future stub returns a ListenableFuture.

Figure 18: Response object when using the synchronous client.
Figure 19: Response object when using the asynchronous client.

Metrics and traces

The application exposes an endpoint under /metrics where you can see a list of available metrics. For instance, the left-hand side picture in Figure 20 presents the overall latency and the right-hand side picture reveals the number of successful responses:

Figure 20: Metrics are reported by the GrpcClientMonitoringInterceptor and show up under /metrics.

Trace data are reported to Zipkin and shown on the Zipkin UI once you run it locally as shown in Figure 21.

Figure 21: Trace of the gRPC call as it shows up in the Zipkin UI.

Conclusion

In this blog we implemented a Spring Boot service in Java which exposes a gRPC endpoint. Although gRPC is not as heavily adopted as REST/HTTP we showed that it is straightforward to implement similar functionality for aspects like logging, monitoring, or tracing.

As pointed out in the first blog, what you get with gRPC is a clear contract, without the need to introduce additional libraries, (de)serialisation out-of-the-box without boilerplate code, and a performance boost from the binary protocol, although this is something that we haven’t discussed further in this blogpost since we focused on the implementation of the service.

https://lifeatexpediagroup.com/brands

--

--