Microservice in Java (Part-1): gRPC server implementation example with Spring and Gradle
Notes on Microservice implementation in Java. Implementing example gRPC hello server with Spring and Gradle.
gRPC
The official website for grpc really gives nice detailed introduction to what it is. With microservice architecture, the services will need to have communication with each other. And in distributed system gRPC enables to have seamless and faster integration with polyglot microservices. We just have to write proto files and the language specific stubs for client and server implementations get generated automatically. Also gRPC uses HTTP/2 which is amazingly fast.
Assuming we already know what is the proto file following shows example of how we can use Gradle and Spring to quickly setup gRPC server in Java in minutes. (If needed to learn about protocol buffers: google link)
hello.proto
The proto used in the example server:
syntax = "proto3";
option java_package = "com.example.hello";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
setting up server
Setup working directory:
$ mkdir grpc-spring-gradle-example
$ cd grpc-spring-gradle-example/
$ mkdir -p src/main/proto
Have to put hello.proto
in above proto directory:
$ cd src/main/proto/
$ vim hello.proto # add above file content and save
build.gradle
Uses gradle plugin and spring & grpc dependencies:
apply plugin: 'com.google.protobuf'
apply plugin: 'application'
apply plugin: 'java'
repositories {
mavenLocal()
mavenCentral()
}
group 'com.example'
version '0.1'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.1"
}
}
dependencies {
compile group: 'io.grpc', name: 'grpc-netty', version: '1.1.2'
compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.1.2'
compile group: 'io.grpc', name: 'grpc-stub', version: '1.1.2'
compile group: 'io.grpc', name: 'grpc-services', version: '1.1.2'
compile group: 'org.springframework', name: 'spring-aop', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-aspects', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-beans', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-context', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-context-support', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-core', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-expression', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-jdbc', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-jms', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-tx', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-web', version: '4.3.5.RELEASE'
compile group: 'org.springframework', name: 'spring-test', version: '4.3.5.RELEASE'
}
subprojects {
repositories {
mavenLocal()
mavenCentral()
}
tasks.withType(JavaCompile) {
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
}
}
sourceSets {
main {
proto {
srcDir 'src/main/proto'
}
java {
// include self written and generated code
srcDirs 'src/main/java', 'generated-sources/main/java', 'generated-sources/main/grpc'
}
}
}
mainClassName = "com.example.HelloMain"
protobuf {
// Configure the protoc executable
protoc {
// Download from repositories
artifact = 'com.google.protobuf:protoc:3.0.2'
}
// Configure the codegen plugins
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.1.2'
}
}
generateProtoTasks.generatedFilesBaseDir = 'generated-sources'
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
Add build.gradle
in the root directory:
$ cd ../../..
$ vim build.gradle # add above file content and save
That it! upon gradle build
it would add the generated stub at the generated-sources
as described in the build.gradle
:
$ gradle build
This would run protoc as we have added the generateProtoTasks
in the gradle file.
After the build is success it would show up as:
├── build.gradle
├── generated-sources
│ └── main
│ ├── grpc
│ │ └── com
│ │ └── example
│ │ └── hello
│ │ └── GreeterGrpc.java
│ └── java
│ └── com
│ └── example
│ └── hello
│ └── Hello.java
└── src
└── main
├── proto
└── hello.proto
adding server with spring
Add spring config files and the server start up files:
└── src
└── main
├── java
│ └── com
│ └── example
│ ├── GreeterServiceImpl.java
│ ├── GrpcService.java
│ ├── HelloMain.java
│ └── HelloServer.java
├── proto
│ └── hello.proto
└── resources
└── spring
└── spring-hello.xml
spring-hello.xml
: describes the application context component scanGreeterServiceImpl.java
: the implementation of grpc service stub, generated from proto. (GreeterGrpc.GreeterImplBase)GrpcService.java
: the marker interface to identify all spring beans which are grpc service stub implementationHelloServer.java
: the grpc server wrapper for all grpc services hosted for this application; as of now we only have one service for the app: GreeterServiceImpl.javaHelloMain.java
: the main method entry point for the java application, which initialises the spring context, starts grpc server
java application
resources/spring/spring-hello.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd ">
<aop:aspectj-autoproxy/>
<context:annotation-config/>
<context:component-scan base-package="com.example">
</context:component-scan>
</beans>
com.example.GreeterServiceImpl
import com.example.hello.GreeterGrpc;
import com.example.hello.Hello;
import io.grpc.stub.StreamObserver;
import org.springframework.stereotype.Service;
@Service
public class GreeterServiceImpl extends GreeterGrpc.GreeterImplBase implements GrpcService {
@Override
public void sayHello(Hello.HelloRequest req, StreamObserver<Hello.HelloReply> responseObserver) {
Hello.HelloReply reply = Hello.HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
com.example.GrpcService
/**
* Marker interface; has to be added for those BindableService which is supposed to be added in matcher server
*
*/
public interface GrpcService {
}
com.example.HelloServer
package com.example;
import io.grpc.BindableService;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.protobuf.service.ProtoReflectionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
@Service
public class HelloServer {
@Autowired
private List<GrpcService> services;
private Server server;
public void start(int port) throws IOException, InterruptedException {
ServerBuilder<?> serverBuilder = ServerBuilder.forPort(port);
for (GrpcService grpcService : services) {
if (!(grpcService instanceof BindableService)) {
throw new RuntimeException("GrpcService should only used for grpc BindableService; found wrong usage of GrpcService for service: " + grpcService.getClass().getSimpleName());
}
serverBuilder.addService((BindableService) grpcService);
}
this.server = ((ServerBuilder<?>) serverBuilder)
.addService(ProtoReflectionService.getInstance())
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
stop();
System.err.println("*** server shut down");
}));
blockUntilShutdown();
}
public void stop() {
if (this.server != null) {
this.server.shutdown();
}
}
/**
* Wait for main method. the gprc services uses daemon threads
* @throws InterruptedException
*/
public void blockUntilShutdown() throws InterruptedException {
if (this.server != null) {
this.server.awaitTermination();
}
}
}
com.example.HelloMain
package com.example;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
import java.util.TimeZone;
public class HelloMain {
private static final int DEFAULT_PORT = 10000;
public static void main(String[] args) throws IOException, InterruptedException {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
//now that we have the configuration files, let the spring context read it and start the message listener container(s).
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath*:/spring/spring*.xml");
context.registerShutdownHook();
int port = getPort(args);
HelloServer server = context.getAutowireCapableBeanFactory().createBean(HelloServer.class);
server.start(port);
}
private static int getPort(String[] args) {
int port = DEFAULT_PORT;
if (args.length > 0) {
try {
String portNum = args[0];
int parsedPort = Integer.parseInt(portNum);
if (parsedPort > 0) {
port = parsedPort;
}
} catch (NumberFormatException ignored) {
}
}
return port;
}
}
Thats it. Running above main method should start the server for greeter service. Above setup is added at github .
References:
- https://grpc.io/docs/guides/
- https://github.com/grpc/grpc-java/
- https://github.com/google/protobuf-gradle-plugin
- https://spring.io/blog/2015/03/22/using-google-protocol-buffers-with-spring-mvc-based-rest-services
- https://blog.bugsnag.com/grpc-and-microservices-architecture/
- https://www.baeldung.com/grpc-introduction