(久しぶりにJavaの話 )
はじめに
みなさん複数のサーバで構成されているサービスってどのように通信してますかね。
今私が担当しているプロジェクトではSpringBootを使って、RESTっぽいAPIを各サーバ用意しています。
SpringBootでAPIを呼ぶときにはよくRestTemplate
が使われると思うのですが、
パスとリクエスト・レスポンスモデルの対応を仕様書等から引っ張ってきたり、ソースコードが見られるならController
から判断してみたりと、何かとクライアント側では考慮することが多いです。(経験上
「Controller
と同じようにクライアント側も書けたらものすごい楽なのに。。。」
「さらにController
と同じインターフェースが使えたらメンテナンスももっともっと楽になるのに。。。」
それFeign(OpenFeign)
を使えばできますよ
Feign(OpenFeign)とは
Feign makes writing java http clients easier
FeignはRetrofit
やJAXRS-2.0
、WebSocket
からインスパイアされたHTTPクライアントためのフレームワークです。上記のように、より簡潔に書けることをウリにしています。
SpringBootで使用するにはSpringCloudの中のSpring Cloud OpenFeignを使います。
※ 昔はFeign
という名前のプロジェクトでしたが、Spring CloudのFinchley.M7
からはOpenFeign
という別のプロジェクトが使われているので注意(使い方は前の時と一緒です)
Spring Cloud Finchley.M7での変更内容
使い方
Livedoorお天気Webサービスを使って天気情報を取得してみます
Java9、Maven(3.5.2)、SpringBoot(2.0.0.RELEASE)で動かします。
Spring Cloud OpenFeignのインストール
pom.xml
はこんな感じ。
spring-cloud.version
に最新(2018/03/02時点)のFinchley.M7
を指定します。
lombokは好みで
<?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>
モデルの実装
簡略化するために、タイトルとディスクリプションのみを扱います。
package com.example.ofc.model;
import lombok.Data;
@Data
public class WeatherInfo {
private String title;
private WeatherDescription description;
}
package com.example.ofc.model;
import lombok.Data;
@Data
public class WeatherDescription {
private String text;
private String publicTime;
}
クライアントの実装(?)
実装といいつつアノテーションでインターフェースを宣言するだけです。
サーバ側の@RestController
と対になるのが@FeignClient
です。
@FeignClient
には名前とベースパスとなるurl
を渡します。
今回はハードコードしていますが、実際のコードではプロパティファイルに外出ししたほうが良いです。
そうすれば環境毎に違うパスを見に行くこともできます。
メソッドはコントローラの実装と同じように@RequestMapping
や@RequestParam
等をつけて宣言します。
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);
}
起動クラス
上記のクライアントを@Autowired
してrun
の中でAPIを呼びます。(今回は東京を指定)
それ以外は普通の起動クラスです。
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 {
// 東京都: 130010
ResponseEntity<WeatherInfo> response = weatherClient.getWeatherInfo(130010L);
log.info(response.getBody().toString());
}
}
起動するとログに天気情報が出力されます。
.
.
.
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=東京都 東京 の天気, description=WeatherDescription(text= 日本付近は冬型の気圧配置
となっています。
【関東甲信地方】
関東甲信地方はおおむね晴れていますが、長野県や関東地方北部の山沿い
では曇りで雪の降っている所があります。
2日は、冬型の気圧配置は次第に緩み高気圧に覆われてくるため、おおむ
ね晴れますが、関東地方北部や長野県の山沿いでは、はじめ寒気の影響によ
り曇りで雪の降る所があるでしょう。
3日は、高気圧に覆われるため、おおむね晴れる見込みです。
関東近海では、2日はうねりを伴ってしける所があり、3日は波が高い所
があるでしょう。船舶は高波に注意してください。
【東京地方】
2日から3日にかけて、晴れるでしょう。, publicTime=2018-03-02T10:40:00+0900))
.
.
.
ほとんど実装せずにできちゃいました!
FeignClientとRestControllerで同一のインターフェースを使いたい場合
先ほどの例は外部のAPIでしたが、内部でのAPI通信のほうが多いのではないでしょうか。
はじめににも書きましたが、クライアント側のFeignClientとサーバ側のRestControllerで同一のインターフェースが使えたらいろいろと楽になりますよね。
そんなときには、クライアントではインターフェースを継承したFeignClientを、サーバではインターフェースを実装したRestControllerを作成すれば良いです。
すると、公開するのはインターフェース(とモデル)のみでよくなり、@ApiResponse
等のアノテーションを使ってステータスコード等の情報も書けば仕様書もいらなくなります。(仕様書が欲しければSwaggerで自動作成する)
先ほどの天気情報を例にします。(import等は省略)
public interface WeatherApi {
@RequestMapping(method = RequestMethod.GET, value = "/forecast/webservice/json/v1")
ResponseEntity<WeatherInfo> getWeatherInfo(@RequestParam("city") Long city);
}
// urlの向き先は適当
@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) {
// 天気情報作成...
return responseEntity;
}
}
なんて簡単なんでしょう!
まとめ
Spring Cloud Open Feignを使ってApiクライアントを作成する方法を紹介しました。
実装量や可用性を考えたら積極的に使っていきたいですね。
今回のサンプルはこちら
https://github.com/totto357/open-feign-client-example