To show how gRPC works let’s build a client and corresponding server that exposes a simple Hello World gRPC service.
2. Defining a Service Using Protocol Buffers
gRPC services are defined using protocol buffers. These are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data.
You specify how you want the information you’re serializing to be structured by defining protocol buffer message types in .proto files. Each protocol buffer message is a small logical record of information, containing a series of name-value pairs.
For this example, we define a first message containing information about a Person and a second message containing a Greeting. Both are then used in a sayHello() RPC method that takes the person message from the client and returns a greeting from the server.
We also define the version of the protocol buffers language that is used (proto3) in addition to package name and an option that enables the generation of separate files for different classes.
The below protocol buffer file is stored in src/main/proto/HelloWorld.proto.
Now that we have defined how the data is structured we need to generate source code that allows us to easily write and read protobuf messages using Java. We will do this in the next section using a Maven plugin.
3. General Project Setup
We will use the following tools/frameworks:
Spring Boot 2.1
Our project has the following directory structure:
Shown below is the XML representation of our Maven project in a POM file. It contains the needed dependencies for compiling and running the example.
In order to configure and expose the Hello World gRPC service endpoint, we will use the Spring Boot project.
To facilitate the management of the different Spring dependencies, Spring Boot Starters are used. These are a set of convenient dependency descriptors that you can include in your application.
We include the spring-boot-starter-web dependency which automatically sets up an embedded Apache Tomcat that will host our gRPC service endpoint.
The spring-boot-starter-test includes the dependencies for testing Spring Boot applications with libraries that include JUnit, Hamcrest and Mockito.
The Spring boot starter for gRPC framework auto-configures and runs an embedded gRPC server with @GRpcService enabled Beans as part of a Spring Boot application. The starter supports both Spring Boot version 1.5.X and 2.X.X. We enable it by including the grpc-spring-boot-starter dependency.
Protocol buffers support generated code in a number of programming languages. This tutorial focuses on Java.
There are multiple ways to generate the protobuf-based code and in this example we will use the protobuf-maven-plugin as documented on the grpc-java GitHub page.
We also include the os-maven-plugin extension that generates various useful platform-dependent project properties. This information is needed as the Protocol Buffer compiler is native code. In other words, the protobuf-maven-plugin needs to fetch the correct compiler for the platform it is running on.
Finally, the plugins section includes the spring-boot-maven-plugin. This allows us to build a single, runnable uber-jar. This is a convenient way to execute and transport our code. Also, the plugin allows us to start the example via a Maven command.
The protobuf-maven-plugin will generate Java artifacts for the HelloWorld.proto file located in src/main/proto/ (this is the default location the plugin uses).
Execute following Maven command, and the different message and service classes should be generated under target/generated-sources/protobuf/.
5. Spring Boot Setup
Create a SpringGrpcApplication that contains a main() method that uses Spring Boot’s SpringApplication.run() method to bootstrap the application, starting Spring.
Note that @SpringBootApplication is a convenience annotation that adds: @Configuration, @EnableAutoConfiguration, and @ComponentScan.
The service implementation is defined in the HelloWorldServiceImpl POJO that implements the HelloWorldServiceImplBase class that was generated from the HelloWorld.proto file.
We override the sayHello() method and generate a Greeting response based on the first and last name of the Person passed in the request.
Note that the response is a StreamObserver object. In other words, the service is by default asynchronous. Whether you want to block or not when receiving the response(s) is the decision of the client as we will see further below.
We use the response observer’s onNext() method to return the Greeting and then call the response observer’s onCompleted() method to tell gRPC that we’ve finished writing responses.
The HelloWorldServiceImpl POJO is annotated with @GRpcService which auto-configures the specified gRPC service to be exposed on port 6565.
7. Creating the Client
The client code is specified in the HelloWorldClient class.
We annotate the client with @Component which will cause Spring to automatically create and import below bean into the container if automatic component scanning is enabled (adding the @SpringBootApplication annotation to the main SpringWsApplication class is equivalent to using @ComponentScan).
To call gRPC service methods, we first need to create a stub.
There are two types of stubs available:
A blocking/synchronous stub that will wait for the server to respond
A non-blocking/asynchronous stub that makes non-blocking calls to the server, where the response is returned asynchronously.
In this example, we will implement a blocking stub.
In order to transport messages, gRPC uses http/2 and some abstraction layers in between. This complexity is hidden behind a MessageChannel that handles the connectivity. The general recommendation is to use one channel per application and share it among service stubs.
We use an init() method annotated with @PostConstruct in order to build a new MessageChannel right after the after the bean has been initialized. The channel is then used to create the helloWorldServiceBlockingStub stub.
gRPC by default uses a secure connection mechanism such as TLS. As this is a simple development test will use usePlaintext() in order to avoid having to configure the different security artifacts such as key/trust stores.
The sayHello() method creates a Person object using the Builder pattern on which we set the 'firstname' and 'lastname' input parameters.
The helloWorldServiceBlockingStub is then used to send the request towards the Hello World gRPC service. The result is a Greeting object from which we return the containing message.
8. gRPC Java Testing
Let’s wrap up by creating a basic unit test case in which the above client is used to send a request to the gRPC Hello World service endpoint. We then verify if the response is equal to the expected greeting.