(Java story after a long time: relaxed :)
How do you communicate with a service that consists of multiple servers?
The project I'm in charge of right now uses Spring Boot to provide REST-like APIs for each server.
I think that RestTemplate
is often used when calling API in Spring Boot,
On the client side, there are many things to consider, such as pulling the correspondence between the path and the request / response model from the specifications, or judging from the Controller
if the source code can be seen. (Experience
"It would be really easy if I could write on the client side as well as Controller
... "
"Furthermore, if the same interface as Controller
can be used, maintenance will be much easier ... "
You can do that with Feign (OpenFeign)
: sunglasses:
https://github.com/OpenFeign/feign
Feign makes writing java http clients easier
Feign is a framework for HTTP clients inspired by Retrofit
, JAXRS-2.0
, and WebSocket
. As mentioned above, I am trying to write more concisely.
To use it with Spring Boot, use Spring Cloud OpenFeign in Spring Cloud.
Feign
, but note that another project called ʻOpenFeign is used from
Finchley.M7` of Spring Cloud (the usage is the same as before).
Changes in Spring Cloud Finchley.M7Try to get the weather information using Livedoor Weather Web Service: partly_sunny:
It runs on Java9, Maven (3.5.2), SpringBoot (2.0.0.RELEASE).
pom.xml
looks like this.
Specify the latest (as of 03/02/2018) Finchley.M7
for spring-cloud.version
.
lombok is your choice
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.ofc</groupId>
<artifactId>open-feign-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>open-feign-client</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>9</java.version>
<spring-cloud.version>Finchley.M7</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
For simplicity, we'll only deal with titles and descriptions.
WeatherInfo.java
package com.example.ofc.model;
import lombok.Data;
@Data
public class WeatherInfo {
private String title;
private WeatherDescription description;
}
WeatherDescription.java
package com.example.ofc.model;
import lombok.Data;
@Data
public class WeatherDescription {
private String text;
private String publicTime;
}
Just declare the interface with annotations while calling it an implementation.
@FeignClient
is paired with @ RestController
on the server side.
Pass the name and base path ʻurl to
@FeignClient`.
This time it's hard code, but in the actual code it's better to go out to the properties file.
Then you can go to see different paths for each environment.
The method is declared with @RequestMapping
, @RequestParam
, etc. as in the controller implementation.
WeatherClient.java
package com.example.ofc.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.ofc.model.WeatherInfo;
@FeignClient(name = "weather", url = "weather.livedoor.com")
public interface WeatherClient {
@RequestMapping(method = RequestMethod.GET, value = "/forecast/webservice/json/v1")
ResponseEntity<WeatherInfo> getWeatherInfo(@RequestParam("city") Long city);
}
Call the API in run
by @Autowired
the above client. (This time, specify Tokyo)
Other than that, it is a normal startup class.
Application.java
package com.example.ofc;
import com.example.ofc.model.WeatherInfo;
import com.example.ofc.api.WeatherClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.http.ResponseEntity;
@SpringBootApplication
@EnableFeignClients
@EnableAutoConfiguration
@Slf4j
public class Application implements CommandLineRunner {
@Autowired
private WeatherClient weatherClient;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... strings) throws Exception {
//Tokyo: 130010
ResponseEntity<WeatherInfo> response = weatherClient.getWeatherInfo(130010L);
log.info(response.getBody().toString());
}
}
When started, the weather information will be output to the log.
.
.
.
2018-03-02 16:00:22.536 INFO 8604 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Located managed bean 'refreshScope': registering with JMX server as MBean [org.springframework.cloud.context.scope.refresh:name=refreshScope,type=RefreshScope]
2018-03-02 16:00:22.550 INFO 8604 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Located managed bean 'configurationPropertiesRebinder': registering with JMX server as MBean [org.springframework.cloud.context.properties:name=configurationPropertiesRebinder,context=2098d877,type=ConfigurationPropertiesRebinder]
2018-03-02 16:00:22.582 INFO 8604 --- [ main] com.example.ofc.Application : Started Application in 2.997 seconds (JVM running for 6.875)
2018-03-02 16:00:22.753 INFO 8604 --- [ main] com.example.ofc.Application : WeatherInfo(title=Tokyo Tokyo weather, description=WeatherDescription(text=Winter-type pressure distribution around Japan
It has become.
[Kanto Koshin region]
The Kanto Koshin region is generally sunny, but along the mountains in Nagano prefecture and the northern part of the Kanto region.
Then there are places where it is cloudy and snowy.
On the 2nd, the winter-type pressure distribution gradually loosens and becomes covered with high pressure, so it is generally
It will be fine, but in the northern part of the Kanto region and along the mountains in Nagano prefecture, due to the influence of cold air at first
There will be places where it will be cloudy and snowy.
It is expected to be mostly sunny on the 3rd due to the high pressure.
In the sea near Kanto, there are places where swells can occur on the 2nd, and where the waves are high on the 3rd.
There will be. Beware of high waves on ships.
[Tokyo District]
It will be fine from 2 to 3 days., publicTime=2018-03-02T10:40:00+0900))
.
.
.
I could do it with almost no implementation! : yum:
The previous example was an external API, but I think there is more internal API communication. As I wrote at the beginning, it would be easier if the same interface could be used for FeignClient on the client side and RestController on the server side.
In such a case, you can create a FeignClient that inherits the interface on the client and a RestController that implements the interface on the server.
Then, only the interface (and model) needs to be published, and if you write the information such as the status code using annotations such as @ ApiResponse
, you do not need the specifications. (If you want a specification, Swagger will create it automatically)
Take the weather information above as an example. (Import etc. omitted)
public interface WeatherApi {
@RequestMapping(method = RequestMethod.GET, value = "/forecast/webservice/json/v1")
ResponseEntity<WeatherInfo> getWeatherInfo(@RequestParam("city") Long city);
}
//The destination of the url is appropriate
@FeignClient(name = "weather", url="localhost:8888")
public interface WeatherApiClient extends WeatherApi {
}
@RestController
public class WeatherController implements WeatherApi {
@Override
ResponseEntity<WeatherInfo> getWeatherInfo(@RequestParam("city") Long city) {
//Weather information creation...
return responseEntity;
}
}
How easy it is! : dizzy_face:
I showed you how to create an API client using Spring Cloud Open Feign. Considering the amount of implementation and availability, I would like to actively use it.
Click here for this sample https://github.com/totto357/open-feign-client-example
Recommended Posts