Are you sick of writing client libraries for all your clients? Do you have trouble sticking to a contract-first approache with REST? Use gRPC! Specifically gRPC with micronaut!
To get familiar with micronaut check out this previous post on micronaut. The complete code for this post can be found on github here.
For a different tutorial on your first gRPC server with micronaut please look at this one from micronaut. Or your starting point is here with the underlying technology of gRPC.
Protobuf
gRPC is contract first i.e. you have to define the specification before you can write your implementation. This contract is defined with Protocol Buffers (protobuf) and come with the .proto
extension.
syntax = "proto3";
package nl.sybrenbolandit.grpc.cat;
// Define the operations of the api
service CatService {
rpc GetCat (CatRequest) returns (Cat) {}
}
// Define the objects involved
message CatRequest {
string chip_id = 1;
}
message Cat {
string name = 1;
int32 age = 2;
}
First we define the protobuf version (currently version 3) and the package
. Then we define all the operations in a service followed by a definition of the input and output objects (messages
).
Out of these proto files the boilerplate code for the client and server side can be generated. We could use the protoc
compiler directly for this (download here) but in the next section we will use a maven plugin to include this step in our build process.
Setup micronaut application
We start by creating a micronaut application from the command line.
mn create-app grpc-cat-api --build=maven
Include the api specification we just created at src/main/proto/cats.proto. Now add the protoc-jar maven plugin.
<plugin>
<groupId>com.github.os72</groupId>
<artifactId>protoc-jar-maven-plugin</artifactId>
<version>3.6.0.2</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<addProtoSources>all</addProtoSources>
<includeMavenTypes>direct</includeMavenTypes>
<inputDirectories>
<include>src/main/proto</include>
</inputDirectories>
<outputTargets>
<outputTarget>
<type>java</type>
</outputTarget>
<outputTarget>
<type>grpc-java</type>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.17.1</pluginArtifact>
</outputTarget>
</outputTargets>
</configuration>
</execution>
</executions>
</plugin>
The plugin will generate the grpc server code in target/generated-sources.
./mvnw generate-sources
The proto compiler created the java code and put it in a single file. To split it into a file per object we can add language specific options to our proto file.
option java_multiple_files = true;
option java_package = "nl.sybrenbolandit.proto";
Here we want multiple files and change the package of the java objects.
gRPC API implementation
Micronaut has a handy dependency based on grpc-java. We can remove the micronaut-http-client and micronaut-http-server-netty dependencies and add the following.
<dependency>
<groupId>io.micronaut.grpc</groupId>
<artifactId>micronaut-grpc-server-runtime</artifactId>
</dependency>
For the implementation of the API we just have to extend the generated class and implement the getCat method.
@Singleton
public class CatEndpoint extends CatServiceGrpc.CatServiceImplBase {
@Override
public void getCat(CatRequest request, StreamObserver<Cat> responseObserver) {
Cat response = Cat.newBuilder()
.setName("Freddy")
.setAge(4)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
Note that we also get builders for all the objects generated by the proto compiler. This is all the code we need for a gRPC endpoint!
Testing gRPC
Because we are working with an observer pattern in the gRPC endpoint the test looks as follows.
@Test
void testGetCat() {
catEndpoint.getCat(CatRequest.newBuilder().build(), new StreamObserver<Cat>() {
@Override
public void onNext(Cat cat) {
assertEquals("Freddy", cat.getName());
assertEquals(4, cat.getAge());
}
@Override
public void onError(Throwable throwable) {
fail("Should not throw error!");
}
@Override
public void onCompleted() {
// Do nothing
}
});
}
Running gRPC
To run the application we can use the maven-exec-plugin and point to our application class.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<executable>java</executable>
<arguments>
<argument>-noverify</argument>
<argument>-XX:TieredStopAtLevel=1</argument>
<argument>-classpath</argument>
<classpath/>
<argument>nl.sybrenbolandit.grpc.cat.api.Application</argument>
</arguments>
</configuration>
</plugin>
And use the following command
./mvnw compile exec:exec
Client libraries
You do not have to implement any clients with gRPC. You can simply communicate the proto file and consumers can generate/implement their own client in their specific language.
Hopefully you are now able to setup your own endpoint in gRPC with micronaut. Happy gRPCing!