Replacing Rest communication with gRPC in microservices architecture where efficiency is critical is a good approach. gRPC is using HTTP/2, which allows multiple requests to be sent over a single connection. Also, it provides code generation tools that generate code for both the client and server, making it easier to write and maintain code.
You can find more about this framework on this page.
I will explain the approach, how we can implement gRPC in projects. For that purpose, I will create 3 projects.
gRPC Interface
In this project, we are going to create the contract between the services, define proto files and generate the classes.
I will create one proto file users.proto and I will update the pom.xml file.
The .proto file contains The definition of the gRPC service.
/src/main/proto/users.proto
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.north47.proto.lib";
option java_outer_classname = "UsersProto";
service UserService {
rpc createUser(CreateUserRequest) returns (UserResponse) {};
rpc updateUser(UpdateUserRequest) returns (UserResponse) {};
}
message CreateUserRequest {
string name = 1;
}
message UpdateUserRequest {
string id = 1;
string name = 2;
}
message UserResponse {
string id = 1;
string name = 2;
}
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.north47</groupId>
<artifactId>grpc-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>grpc-interface</name>
<description>Demo project for Spring Boot</description>
<properties>
<protobuf.version>3.17.3</protobuf.version>
<protobuf-plugin.version>0.6.1</protobuf-plugin.version>
<grpc.version>1.42.1</grpc.version>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<!-- Java 9+ compatibility - Do NOT update to 2.0.0 -->
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-plugin.version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
More about the syntax you will find if you visit Protocol Buffers Documentation.
Next we are going to run mvn clean install
command and we are going to generate all of the classes that we are going to need. Also this interface project we are going to add as a dependency to the gRPC server and gRPC client projects.
gRPC Server
In the server project, we are going to update the pom.xml
file with grpc-server-spring-boot-starter and grpc-interface. The key dependencies for server project are: grpc-server-spring-boot-starter and grpc-interface.
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.north47</groupId>
<artifactId>grpc-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>grpc-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.north47</groupId>
<artifactId>grpc-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
And now we will extend UserServiceGrpc.UserServiceImplBase that is coming from gRPC interface library, and add annotation @net.devh.boot.grpc.server.service.GrpcService.
UserService.java
package com.north47.grpcserver.proto;
import com.north47.proto.lib.CreateUserRequest;
import com.north47.proto.lib.UpdateUserRequest;
import com.north47.proto.lib.UserResponse;
import com.north47.proto.lib.UserServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
import java.util.UUID;
@GrpcService
public class UserService extends UserServiceGrpc.UserServiceImplBase {
@Override
public void createUser(CreateUserRequest request, StreamObserver<UserResponse> responseObserver) {
UserResponse userResponse = UserResponse.newBuilder()
.setId(UUID.randomUUID().toString())
.setName(request.getName())
.build();
responseObserver.onNext(userResponse);
responseObserver.onCompleted();
}
@Override
public void updateUser(UpdateUserRequest request, StreamObserver<UserResponse> responseObserver) {
super.updateUser(request, responseObserver);
}
}
If we start gRPC project in the console we will see that gRPC server is started.
n.d.b.g.s.s.GrpcServerLifecycle : gRPC Server started, listening on address: *, port: 9090
gRPC Client
In the client project, I will modify pom.xml
file and I will write one test to make the call to the gRPC server.
In the client project key dependencies are: grpc-client-spring-boot-starter and grpc-interface.
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.north47</groupId>
<artifactId>grpc-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>grpc-client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.north47</groupId>
<artifactId>grpc-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
And in the test, I will inject the generated service and drop some configuration in application.yml
file.
@SpringBootTest
class GrpcClientApplicationTests {
@GrpcClient("userService")
private UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub;
@Test
void test() {
CreateUserRequest createUserRequest = CreateUserRequest.newBuilder()
.setName("John")
.build();
UserResponse userResponse = userServiceBlockingStub.createUser(createUserRequest);
assertEquals(createUserRequest.getName(), userResponse.getName());
assertNotNull(userResponse.getId());
}
}
And configuration that we should add is:
grpc:
client:
userService:
address: 'static://localhost:9090'
enable-keep-alive: true
keep-alive-without-calls: true
negotiation-type: plaintext
userService is the name of the gRPC client that we are configuring, as you can see when we are adding the annotation @GrpcClient we are providing the name of the client we configured in the yaml file.
You can find more configuration parameters in the GrpcChannelProperties java doc page.
When we finish with all three projects, we can start the server and run the test in a client project.
All of the repositories you can find in the links below.
- https://bitbucket.org/n47/grpc-interface/src/master/
- https://bitbucket.org/n47/grpc-server/src/master/
- https://bitbucket.org/n47/grpc-client/src/master/