This post presents the steps required to create a java gRPC client.
The gRPC is a great library/protocol providing inter-microservices communication, with high performance, and multiple programming language support. The definition in the official gRPC site is:
"
RPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication.
"
Let's jump directly into the files required to implement the java gRPC client.
The .proto File
The proto file describe the structures used for the communication, as well as the services.
syntax = "proto3";
package api;
option java_package = "org.alon.grpc.generated";
service MyServer {
rpc UpdateStore(Request) returns (Response) {}
}
enum Action {
ADD = 0;
REDUCE = 1;
}
message Request {
string name = 1;
uint64 update = 2;
Action action = 3;
}
message Response{
uint64 price = 1;
}
We have configured the structure of the Request and the structure of the Response.
In addition, we have configured the service API: UpdateStore.
The Maven pom file
The pom.xml is based on a standard java application pom file, and includes the following:
- protoc and gRPC related dependencies
- a protobuf-maven-plugin to generate the protoc and gRPC APIs. The following files are generated, and automatically added to the project sources:
- org.alon.grpc.generated.Api - the request builder class
- org.alon.grpc.generated.MyServerGrpc - the server communication API
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.alon</groupId>
<artifactId>grpc</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.9</maven.compiler.source>
<maven.compiler.target>1.9</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.22.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.22.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.22.1</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<build>
<extensions>
<!--
generates various useful platform-dependent project properties normalized from ${os.name} and ${os.arch}
This is required for running the protobuf plugin
-->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.alon.grpc.GrpcClient</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<!--
copy the proto files to the local folder.
For actual user: You can manually copy the proto files to the folder: target/proto
and the comment this plugin.
-->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>copy-protoc-files</id>
<phase>generate-sources</phase>
<configuration>
<tasks>
<copy file="src/main/proto/api.proto"
tofile="target/proto/api.proto"
overwrite="true"
/>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<!--
run the protoc to generate Java source from the proto files
-->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.22.1:exe:${os.detected.classifier}</pluginArtifact>
<protoSourceRoot>target/proto</protoSourceRoot>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<id>run-protoc</id>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
The Java Source
The last piece is the java source that we create to use the generated gRPC source.
package orig.alon.grpc;
import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
import org.alon.grpc.generated.Api;
import org.alon.grpc.generated.MyServerGrpc;
import java.io.File;
import java.util.concurrent.TimeUnit;
public class Client {
private final ManagedChannel channel;
private final MyServerGrpc.MyServerBlockingStub serverApi;
public Client(String host, int port, String certificate) throws Exception {
SslContext sslContext = GrpcSslContexts.forClient()
.trustManager(new File(certificate)).build();
channel = NettyChannelBuilder.forAddress(host, port).sslContext(sslContext).build();
serverApi = MyServerGrpc.newBlockingStub(channel);
}
public void shutdown() throws Exception {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
private long sendRequest(Api.Action action, String name, long price) {
Api.Request request = Api.Request.newBuilder()
.setAction(action)
.setName(name)
.setUpdate(price)
.build();
Api.Response response = serverApi.updateStore(request);
return response.getPrice();
}
public long add(String name, long price) {
return sendRequest(Api.Action.ADD, name, price);
}
public long reduce(String name, long price) {
return sendRequest(Api.Action.REDUCE, name, price);
}
public static void main(String[] args) throws Exception {
System.out.println("Starting");
if (args.length != 3) {
System.err.println("Wrong amount of arguments");
System.err.println("Usage:");
System.err.println("HOST PORT SERVER_CERTIFICATE_FILE_PATH");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
String certificate = args[2];
Client client = new Client(host, port, certificate);
try {
System.out.println(client.add("p1", 1));
System.out.println(client.reduce("p2", 5));
} finally {
client.shutdown();
}
System.out.println("Done");
}
}
Our code receives the host, port, and the certificate file that are used to create connection to the gRPC server. We nicely wrap the gRPC APIs with out own methods, and activate the gRPC server API.