[JAVA] Try gRPC in Spring Boot & Spring Cloud project (Mac OS)

[Updated at 18:00, May 05, 2019] Recently, gRPC seems to be more popular than REST for inter-node communication on distributed systems, so I tried it. In addition, gRPC is provided as a library in java, and in Spring, a starter for boot is provided from LogNet. It seems that there is, so use it on Spring Boot. Also, I'm assuming that it will be used on a distributed system if it is to be put to practical use in the future, so I decided to incorporate it into the Spring Cloud project and run it.

Surprisingly, there were some addictive points before I just moved them, so this time I will share those points and describe what I confirmed to the point where the project works normally.

environment

Project structure

I may mention what is implemented in Spring Cloud separately, but I tried gRPC communication in the following project for load balancing by Eureka and Ribbon.

プロジェクト構成

When the user sends an HTTP request to the above eureka-lb-demo, API to eureka-lb-demoeureka-client-demo via gRPC communication. In the form of going to call. Since the project name is given according to the structure of Eureka, it will be the opposite, but as gRPC

Is the relationship.

eureka-demo, config-server-demoHas nothing to do with this theme. The only part related to the theme is the white part.

プロジェクト構成2

Service content

It's not the essence of this time, but I'll briefly explain it so that you don't lose track of what you're doing when you look at the code snippet. This time, we implemented a service that allows you to obtain equipment information by passing the school name as a parameter. When the request is received by eureka-lb-demo, it internally queries the API of eureka-client-demo called `` `getEquipmentInfo. Therefore, the interface definition this time is named EquipmentService```.

Premise

Before moving on to implementation, I will briefly explain the premise of gRPC. gRPC defines a file called .proto and compiles it using a compiler called protoc, and the source of the API part is automatically generated. Same feeling as SOAP. After creating this file, create a classpath called src / main / proto and build it.

In maven, if you define a plugin, you can add an automatically generated source on the build path just by building as usual.

The reason for the name ** proto ** is that it uses a technology called Protocol Buffers, but the explanation is omitted because it goes against the purpose of this time.

Implementation

Common to Server and Client

The proto file was created like this. What I want to do is return the object as a list type if the parameter school name exists in the DB. The specification is that only the message is returned if it does not exist. Therefore, in EquipmentResponse, the message is defined as the first field, and the list corresponding to java objects is defined as the second field.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.example.ek.eureka_client_demo.equipment";

package equipment_proto;

service EquipmentService {
	rpc getEquipment (EquipmentRequest) returns (EquipmentResponse);
}

message EquipmentRequest{
	string schoolName = 1;
}

message EquipmentResponse{
	string message = 1;
	repeated EquipmentInfo equipmentInfo = 2;
}

message EquipmentInfo{
	string catagory = 1;
	string name = 2;
}

The getEquipment defined in the EquipmentService is the API for acquiring equipment information.

See below for the detailed grammar of proto. Protocol Buffers Language Guide (proto3)

Server side (eureka-client-demo)

<!-- gRPC server -->
<dependency>
  <groupId>io.github.lognet</groupId>
  <artifactId>grpc-spring-boot-starter</artifactId>
</dependency>

Next, since it is necessary to use the above-mentioned protocol, add the following under the ``` ` `` tag. Please also refer to the reference article [^ 1] for this area.


<extensions>
  <extension>
    <groupId>kr.motd.maven</groupId>
    <artifactId>os-maven-plugin</artifactId>
    <version>1.5.0.Final</version>
  </extension>
</extensions>

//Abbreviation

<plugin>
  <groupId>org.xolstice.maven.plugins</groupId>
  <artifactId>protobuf-maven-plugin</artifactId>
  <version>0.5.1</version>
  <configuration>
    <protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
    <pluginId>grpc-java</pluginId>
    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.18.0:exe:${os.detected.classifier}</pluginArtifact>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>compile</goal>
        <goal>compile-custom</goal>
      </goals>
    </execution>
  </executions>
</plugin>

First, an error occurred here. On my MacOS, I got an error saying that $ {os.detected.classifier}` `` could not be identified, so I specified the property and surpassed it. It seems that profile settings can be used with settings.xml```, but I specify the properties on the same pom file. Please note that it seems that you can go or not depending on the OS because you can go in the state of variables in windows.

<properties>
  <java.version>1.8</java.version>
  <spring-cloud.version>Greenwich.RC2</spring-cloud.version>
  <os.detected.classifier>osx-x86_64</os.detected.classifier>
</properties>

Next, if you build in this state, the source code for gRPC will be automatically generated. Implement a Controller that accepts requests from eureka-lb-demo (client side) using the automatically generated code. The created Controller is as follows.


package com.example.ek.eureka_client_demo.equipment;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.collections.CollectionUtils;
import org.lognet.springboot.grpc.GRpcService;
import org.springframework.stereotype.Controller;

import com.example.ek.eureka_client_demo.equipment.EquipmentServiceGrpc.EquipmentServiceImplBase;

import io.grpc.stub.StreamObserver;

@GRpcService // (1)
@Controller
public class EquipmentController extends EquipmentServiceImplBase { // (2)

	private static Map<String, List<Equipment>> schoolEquipmentDB; // (3)

	@Override
	public void getEquipment(EquipmentRequest req, StreamObserver<EquipmentResponse> resObserber) { // (4)
		String schoolNm = req.getSchoolName(); // (5)
		List<Equipment> list = schoolEquipmentDB.get(schoolNm);

		EquipmentResponse equipmentResponsse = null;
		if (CollectionUtils.isEmpty(list)) {
			equipmentResponsse = EquipmentResponse.newBuilder()
					.setMessage(
							"There is no equipment in this scool " + schoolNm + System.getProperty("line.separator"))
					.build(); // (6)
			resObserber.onNext(equipmentResponsse); // (8)
		}
		//When equipment information can be obtained
		else {
			EquipmentResponse.Builder responseBuilder = EquipmentResponse.newBuilder();
			for (Equipment eq : list) {
				EquipmentInfo eqInfo = EquipmentInfo.newBuilder()
						.setCatagory(eq.getCategory())
						.setName(eq.getName())
						.build(); // (7)
				responseBuilder.addEquipmentInfo(eqInfo);
			}
			EquipmentResponse res = responseBuilder.setMessage("").build();
			resObserber.onNext(res); //(8)
		}
		resObserber.onCompleted(); // (9)
	}

	// (3)
	static {
		schoolEquipmentDB = new HashMap<String, List<Equipment>>();

		List<Equipment> lst = new ArrayList<Equipment>();
		Equipment eqp = new Equipment("chair", "High Grade Chair I");
		lst.add(eqp);
		eqp = new Equipment("desk", "High Grade Desk I");
		lst.add(eqp);

		schoolEquipmentDB.put("abcschool", lst);

		lst = new ArrayList<Equipment>();
		eqp = new Equipment("chair", "Low Grade Chair I");
		lst.add(eqp);
				eqp = new Equipment("desk", "Low Grade Desk I");
				lst.add(eqp);

		schoolEquipmentDB.put("xyzschool", lst);

		//Check inside the DB
		System.out.println("School Equipment information are as below.");
		for (Entry<String, List<Equipment>> entry : schoolEquipmentDB.entrySet()) {
			System.out.println("School: " + entry.getKey());
			for (Equipment e : entry.getValue()) {
				System.out.println("    Equipment:" + e.getCategory() + "," + e.getName());
			}
		}

	}
No. Description
(1) Add this annotation when using gRPC.
(2) Automatically generatedEquipmentServiceImplBaseInherit. There are some automatically generated files,.Service name specified by ptoro+ ImplBaseIs.
(3) Since it was troublesome to build a DB, a DB mock that just puts values in variables created with static.
(4) me too.Method name specified by protoOverride.
(5) .protoAs defined in, in the requestschoolNameSince we defined the parameter, get from the request object and get the value from the DB mock. Below this is the source for returning a response.
(6) The response also sets the value of the automatically generated bean with the builder pattern. Here, only a message is set when equipment information cannot be obtained.
(7) This is a branch when a value is taken, not a message, but an object of list elementsEquipmentInfo(This is also an auto-generated source)Set the value to. Like the response object, this also creates an object with the builder pattern.
(8) It is an argument of the methodStreamObserverofonNextSend a value to the client side by setting a response object in the method argument.
(9) Finally to end the communicationonCompleteCall the method.

Also, property settings for gRPC

application.yml


## grpc settings
grpc:
 port: 6565

Then, try building and booting with `` `$ mvn clean install spring-boot: run```. I got the following error.

***************************
APPLICATION FAILED TO START
***************************

Description:

An attempt was made to call the method com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;CLjava/lang/Object;)
V but it does not exist. Its class, com.google.common.base.Preconditions, is available from the following locations:

    jar:file:/Users/***/.m2/repository/com/google/guava/guava/16.0/guava-16.0.jar!/com/google/common/base/Preconditions.class

When I looked it up, there was a Git issue that the guava library was old and should be updated, so when I looked up the source of guava with `` `$ mvn dependency: tree```

[INFO] |  +- com.netflix.eureka:eureka-client:jar:1.9.8:compile
[INFO] |  |  +- org.codehaus.jettison:jettison:jar:1.3.7:runtime
[INFO] |  |  |  \- stax:stax-api:jar:1.0.1:runtime
[INFO] |  |  +- com.netflix.netflix-commons:netflix-eventbus:jar:0.3.0:runtime
[INFO] |  |  |  +- com.netflix.netflix-commons:netflix-infix:jar:0.3.0:runtime
[INFO] |  |  |  |  +- commons-jxpath:commons-jxpath:jar:1.3:runtime
[INFO] |  |  |  |  +- joda-time:joda-time:jar:2.10.1:runtime
[INFO] |  |  |  |  \- org.antlr:antlr-runtime:jar:3.4:runtime
[INFO] |  |  |  |     +- org.antlr:stringtemplate:jar:3.2.1:runtime
[INFO] |  |  |  |     \- antlr:antlr:jar:2.7.7:runtime
[INFO] |  |  |  \- org.apache.commons:commons-math:jar:2.2:runtime
[INFO] |  |  +- com.netflix.archaius:archaius-core:jar:0.7.6:compile
[INFO] |  |  |  \- com.google.guava:guava:jar:16.0:compile

It seems that the jar of eureka-client and the boot-starter of grpc do not match. Exlude the jar of eureka-client as follows and rebuild & execute.

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  <exclusions>
          <exclusion>
              <groupId>com.google.guava</groupId>
              <artifactId>guava</artifactId>
          </exclusion>
    </exclusions>
</dependency>

In addition, since Spring Cloud type projects basically include guava jar, it is necessary to match the version properly when using gRPC. I also added config server and hystrix to dependency, and different versions of guava were included in each, so I also added exclude settings there.

The build result was successful.

Started EurekaClientDemoApplication in 15.476 seconds (JVM running for 56.095)

o.l.springboot.grpc.GRpcServerRunner     : gRPC Server started, listening on port 6565.

Client side (eureka-lb-demo)

Like the Server side, pom excludes guava and just adds the boot-starter of gRPC, so I will omit it. Also, the `` `.proto``` file is exactly the same as the one on the Server side, and the build → source code is automatically generated, which is not unique to the Client side. Since the content unique to the Client side is only the calling method (java code), only the Controller is described.

@RestController
public class SchoolController {

	@Autowired
	EurekaClient client; // (1)

  //abridgement

	@GetMapping(value = "/getEquipmentInfo/{schoolname}")
	public String getEquipments(@PathVariable String schoolname) {
		System.out.println("Getting School details for " + schoolname);

		InstanceInfo instance = client.getNextServerFromEureka("studentService", false);
		ManagedChannel channel = ManagedChannelBuilder.forAddress(instance.getIPAddr(), 6565)
				.usePlaintext().build(); // (2)

		EquipmentServiceBlockingStub stb = EquipmentServiceGrpc.newBlockingStub(channel);
		EquipmentRequest req = EquipmentRequest.newBuilder().setSchoolName(schoolname).build();
		EquipmentResponse res = stb.getEquipment(req); // (3)

		if (StringUtils.hasLength(res.getMessage())) {
			return res.getMessage();
		}

		StringBuilder sb = new StringBuilder();
		for (EquipmentInfo eq : res.getEquipmentInfoList()) {
			sb.append(editResponseForEquipment(eq));
			sb.append(System.getProperty("line.separator"));
		}

		return sb.toString();
	}


	private String editResponseForEquipment(EquipmentInfo eq) {
		return "Category: " + eq.getCatagory() + ", " + "EquipmentName: " + eq.getName() + ";";
	}
No. Description
(1) eureka-client-demoTo get information on the sideEurekaClientDI.
(2) To use gRPCMannagedChannelTo use. For channel generationManagedChannelBuilder.forAddressToeureka-client-demoPass the IP and port of. I eureka-client-I have hard-coded the port because I have a REST port for communication with demo, but if I only use gRPCinstance.getPortIs okay.
(3) This is an automatically generated stub class like the way this library is writtenEquipmentServiceBlockingStubCommunicate using. After setting the parameters in the request, the reststb.getEquipmentThe request is set in the argument of the method and the response is received.

Run

On the other hand, if you make an HTTP request, you can get the result safely as follows.

$ curl http://localhost:8086/getEquipmentInfo/abcschool
Category: chair, EquipmentName: High Grade Chair I;
Category: desk, EquipmentName: High Grade Desk I;

When a school name that does not exist in the DB is specified

$ curl http://localhost:8086/getEquipmentInfo/defschool
There is no equipment in this school defschool

Therefore, it can be seen that the list type of the object and the message of String can communicate as intended.

Source code

The source is as follows, although various other contents are mixed. Source code

In addition, all property setting values not related to gRPC are aggregated in `config-server-demo```, and each project has `src / main / resource / bootstrap.yml``` in config-server. When browsing because it is supposed to go to the setting value for the project (config) Please be careful.

reference

Recommended Posts

Try gRPC in Spring Boot & Spring Cloud project (Mac OS)
Try Spring Boot on Mac
Launch (old) Spring Boot project in IntelliJ
Create Java Spring Boot project in IntelliJ
Run a Spring Boot project in VS Code
Java tips-Create a Spring Boot project in Gradle
View the Gradle task in the Spring Boot project
Microservices in Spring Cloud
Set context-param in Spring Boot
Try Spring Boot from 0 to 100.
Spring Boot 2 multi-project in Gradle
Major changes in Spring Boot 1.5
NoHttpResponseException in Spring Boot + WireMock
Try using Spring Boot Security
Try using gRPC in Ruby
Spring Boot Hello World in Eclipse
Spring Boot application development in Eclipse
gRPC on Spring Boot with grpc-spring-boot-starter
RSocket is supported in Spring Boot 2.2, so give it a try
Try mixing C and Swift in one project (OS X, Linux)
Write test code in Spring Boot
Spring Boot: Restful API sample project
What is @Autowired in Spring boot?
Google Cloud Platform with Spring Boot 2.0.0
Implement Spring Boot application in Gradle
Try running Spring Boot on Kubernetes
Thymeleaf usage notes in Spring Boot
Try mixing more C and Swift in one project (OS X, Linux)
Create a Spring Boot project in intellij and exit immediately after launching
A story about a Spring Boot project written in Java that supports Kotlin
Try to solve Project Euler in Java
Spring Boot environment construction memo on mac
Build Spring Boot + Docker image in Gradle
Static file access priority in Spring boot
Output Spring Boot log in json format
Local file download memorandum in Spring Boot
Try using Spring Boot with VS Code
Use DynamoDB query method in Spring Boot
Try Spring Boot 1 (Environment construction ~ Tomcat startup)
DI SessionScope Bean in Spring Boot 2 Filter
Change session timeout time in Spring Boot
SameSite cookie in Spring Boot (Spring Web MVC + Tomcat)
Test controller with Mock MVC in Spring Boot
Asynchronous processing with regular execution in Spring Boot
Output request and response log in Spring Boot
Put multiple Main classes in one Spring Project
Try LDAP authentication with Spring Security (Spring Boot) + OpenLDAP
Try using GCP's Cloud Vision API in Java
Try to implement login function with Spring Boot
Use Servlet filter in Spring Boot [Spring Boot 1.x, 2.x compatible]
How to add a classpath in Spring Boot
Build Spring Boot project by environment with Gradle
How to bind to property file in Spring Boot
Try to automate migration with Spring Boot Flyway
Annotations used in Spring Boot task management tool
Create Spring Cloud Config Server with security with Spring Boot 2.0
Until you create a Spring Boot project in Intellij and push it to Github
Java development environment construction (Mac + Pleiades All in One Eclipse 4.7 + Spring Boot + Gradle (Buildship))