A little bit of everything

情報系大学院生の備忘録

Spring Feign でRESTクライアントを作る

Spring Feign とは

Spring プロジェクトの1つで、RESTクライアントを簡単に作るためのフレームワークです。

SpringでRESTクライアントというと、RestTemplateクラスを使う方法がすぐに思いつくと思いますが、Spring Feignを使うとRestTemplateのような実装を書く必要がありません。

Spring Feignの公式ドキュメントには、Feign は "declarative REST client"であると書かれています。
「宣言的RESTクライアント」という感じでしょうか。

イメージとしては、通信先のAPIと通信するインタフェースクラスだけ作ってあげると、Feignのフレームワークが裏でRESTクライアントの実装を作って、REST通信をしてくれる感じです。

Spring Feignを使ったサンプルを作る

Spring Boot で Spring Feignを使う例を作ります。
今回作成したプログラム全体は、Githubにて公開しています。

github.com

1. ブランクプロジェクトを作る

Spring Initializr から、Spring Bootのブランクプロジェクトを作ってください。
今回は Maven を使いますので、 Gradle の方は適宜読み替えてください。

https://start.spring.io/

2. POMの編集

まず、 以下のdependency を追加します。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

また、以下のdependencyManagementも追加します。
verision は "Greenwich.SR3" としていますが、最新版が良ければ、Maven のCentral Repositoryで、最新のバージョンを確認してください。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

3. プログラミング

実際にプログラムを書いていきます。
といっても、先に述べたとおり Feign は「宣言型RESTクライアント」なので、基本的にはプログラムに書くのは「宣言」の部分です。

Spring Feign を使用してWeb APIを呼び出す場合、大きく分けて以下の3つを行う必要があります。

  1. Spring Feign を有効にするために、Mainクラスに@EnableFeignClients アノテーションを付与する。
  2. Feign Client(=RESTクライアント)を作成する。
  3. APIの実行結果を受け取るために、リソースクラスを作成する。

これら3つについて、以下に1つずつ解説します。

3.1. Mainクラスに@EnableFeignClients アノテーションを付与する

Spring Feign を有効にするために、Mainクラスに @EnableFeignClients を付与します。
このアノテーションをつけることで、Feign Client のコンポーネントスキャンが有効になります。

@SpringBootApplication
@EnableFeignClients
public class SampleSpringFeignApplication {
    public static void main(String[] args) {
            //省略。詳しくはGithub参照
    }
}


3.2. Feign Client を作る

次に、Feign Client(=RESTクライアント)を作っていきます。Feign Client を作るには、@FeignClient アノテーションを付与したインターフェースを作ります。

今回は、OpenWeatherMap の weather API(場所を指定するとその場所の現在の天気データを返してくれるAPI)を叩くRESTクライアント(Feignクライアント)を作りたいと思います。
OpenWeatherMap のAPIは、事前のアカウント登録で払い出されたAPIキーを、APIを叩く際に一緒に送信する必要があります。
OpenWeatherMapについて、より詳しく知りたい方は以前このブログで書いた記事をご参照ください。
yuukiyg.hatenablog.jp



実際に作成したFeignClientインタフェースです。

@FeignClient(value = "weatherApi", url = "http://api.openweathermap.org/data/2.5")
public interface WeatherFeignClient {

    @RequestMapping(method=RequestMethod.GET, value="weather", produces = "application/json")
    CurrentWeather getCurrentWeather(@RequestParam("q") String cityName, @RequestParam("APPID")String appKey);
}

@FeignClientアノテーションvalue 属性は、作成するRESTクライアントの名前を表す値で、任意の値です。Ribbonというクライアントサイドのロードバランサを作るときに、このvalueが使用されます(今回はRibbonは作りません)。
今回はとりあえず、"weatherApi" という名前を付けました。

@FeignClient アノテーションを付与したインターフェースには、自由にメソッドを作れます。どの単位でメソッドを作るのかもプログラマの自由です。参考までに、このブログの最後に「Feignクライアントのメソッドを分ける際の考え方」を載せています。


繰り返しになりますが、ここで作ったのは、あくまでもインターフェースです。
インターフェースは実装(implements) して使用するのが一般的ですが、Feign では実装クラスを書くことは不要です。
実装は Sring Feign のフレームワークが裏で勝手に作ってくれます(ちなみに、裏では RestTemplate が使われています)。

つまり、プログラマが作るのは、RESTクライアントの「宣言」部分である、インターフェースクラスです。 ここが、Feign が「宣言的RESTクライアント」であると言われている所以だと思います。

3.3 APIを叩いた際に返ってくるリソースに対応するJavaクラスを作る

APIを叩いて、返ってきた結果を受け取るためのリソースクラスを作成します。
リソースクラスという名前が付いていますが、簡単に言えば値を入れるための入れ物のクラスです。

今回使用する OpenWeatherMap のAPI のレスポンスJSONの仕様は、以下のページの真ん中らへんに書かれています。
Current weather data - OpenWeatherMap

このJSONを受けたときに、情報を格納するクラス(リソースクラス)を作っていきます。
以下の画像のように、JSONに対応するクラスを作成しようと思います。
JSONがネストしている構造になっているため、それを受けるJavaクラスもネストした構造にする必要がありそうです。


f:id:yuukiyg:20191118162931p:plain

実際に作成したクラスは以下のとおりです。

全体の情報を格納するCurrentWeatherクラス
@Data
@ToString
public class CurrentWeather {
    // 場所の名前
    private String name;

    // 緯度・経度
    private Coordinate coord;

    // 空模様(晴れ、くもりなど)
    private WeatherCondition[] weather;
    
    // 詳細な気象データ(温度、湿度、気圧など)
    private WeatherDetail main;

    // 風のデータ(風速、風向)
    private Wind wind;

    // 雲量(空の全天に占める雲の割合 %)
    private Cloudiness clouds;

    // 視程(大気の見通し具合 メートル)
    private long visibility;
}


緯度経度を表すCoordinateクラス
@Data
@ToString
public class Coordinate {
    private long lon; // 緯度
    private long lat; // 経度
}


空模様を表す WeatherConditionクラス
@Data
@ToString
public class WeatherCondition {
    private int id;    // 空模様を表すID
    private String main;  // 空模様パラメータのグループ(Rain, Snow, Extreme, など)
    private String description;  //グループ内の空模様
    private String icon;       //空模様を表すアイコン
}


天気の詳細を表す WeatherDetailクラス
@Data
@ToString
public class WeatherDetail {
    private long temp;     //温度
    private long pressure; //気圧
    private long humidity; //湿度
    private long temp_min; //最低気温
    private long temp_max; //最高気温
    // sea_levelやgrnd_levelは省略
}


風の情報を表すWindクラス
@Data
@ToString
public class Wind {
    private long speed;  //風速
    private long deg;  //風向
}


雲量を表すCloudinessクラス
@Data
@ToString
public class Cloudiness {
    private long all;  // 雲鷹。%で表す。
}

4. 動作確認

上記で作成したWeatherFeignClientのgetCurrentWeather()メソッドを実行します。

CurrentWeather currentWeather = feignClient.getCurrentWeather("Tokyo", "YOUR_APP_KEY");

再掲になりますが、プログラムの全体はGithubで公開しています。
github.com

実行すると、CurrentWeatherクラスに、ちゃんと情報が格納された状態になると思います。

CurrentWeather(name=Tokyo, coord=Coordinate(lon=139, lat=35), weather=[WeatherCondition(id=803, main=Clouds, description=broken clouds, icon=04n)], main=WeatherDetail(temp=293, pressure=1009, humidity=73, temp_min=291, temp_max=294), wind=Wind(speed=10, deg=200), clouds=Cloudiness(all=75), visibility=10000)

これは、裏でSpring FeignのフレームワークがRESTのAPIを投げて、その結果をCurrentWeatherクラスにマッピングしてくれているためです。
実装を書く必要がなく、「このAPIを実行して、このクラスに結果を詰めてほしい」という感じのプログラムを書くだけで良いので、とても便利です!

参考:Feignクライアントのメソッドを分ける際の考え方

@FeignClient アノテーションを付与したインターフェースには、自由にメソッドを作れます。どの単位でメソッドを作るのかもプログラマの自由ですが、少なくとも以下のようなポイントでメソッドを分ける必要があると思います。

  • APIを呼ぶ際のHTTPメソッド(GETとかPOST)が違うのであれば、Feignクライアントのメソッドも分ける。例えば、GETで呼ぶAPIとPOSTで呼ぶは、別メソッドとして定義する。

  • 呼ぶAPI名(今回の場合は "weather" というAPI名)が違うのであれば、Feignクライアントのメソッドを分ける。例えば、次の2つはドメイン名が一緒だが、末端のAPI名が違うので、メソッドも分けて定義すべき。

  • APIに渡すパラメータ(クエリパラメータ)が異なるのであれば、Feignクライアントのメソッドも分ける。例えば、次の2つはドメイン名とAPI名が一緒だが、渡すクエリパラメータが違うので、メソッドを分けて定義すべき。