LoginSignup
5

More than 5 years have passed since last update.

インターフェースだけでAPIクライアントを実装するFeignがすごく便利!

Last updated at Posted at 2018-04-03

1. Feignとは

Feignとは非常にシンプルなJavaのHTTPクライアント用ライブラリです。
githubで:star:2745の評価(2018/4/2時点)ので、かなり使われているライブラリのようです。
とても短いコードでHTTPリクエストを発行することができます。Feignを利用する際の流れは以下のような感じです。

デメリットとしてはテキストベースのHTTPリクエストしか発行できない、HTTPレスポンスはBODYにしかアクセスできない等の制限があります。

(2018/4/8 追記)
Feignのデフォルトでは対応していませんが、別モジュールや独自の実装でバイナリのHTTPリクエストを扱えます。

2. ライブラリの用意

Feginは利用するコンポーネントを入れ替えることが可能な構成になっています。
今回はOkHttpJacksonLogbackを利用する構成とします。

pom.xml
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>9.5.1</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
    <version>9.5.1</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-slf4j</artifactId>
    <version>9.5.1</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>

3. APIのインターフェースを定義

呼び出したいAPIをインターフェースとして定義します。

  • @RequestLineで発行したいHTTPリクエストのHTTPメソッドとパスを設定
  • パスの一部を{変数名}でパラメータを参照できる
  • パラメータはメソッド引数に@Paramを付与して定義
  • HTTPヘッダに設定したい内容があれば@Headersで設定

今回は以下に示すAPIを呼び出す場合を考えてみたいと思います。

項番 パス HTTPメソッド 説明
1 /todos GET 全てのTODOを取得する
2 /todos/{todoId} GET todoIdで指定されたTODOを取得する
3 /todos POST 送信されたデータでTODOを作成する
コンテントタイプはapplication/jsonとする
4 /todos/{todoId} PUT todoIdで指定されたTODOを更新する
コンテントタイプはapplication/jsonとする
5 /todos/{todoId} DELETE todoIdで指定されたTODOを削除する

APIのエンドポイント(URL)はhttp://localhost:8090/todo-rest/api/v1とします。

TodoApi.java
package com.example.feign.demo;

import java.util.List;

import feign.Headers;
import feign.Param;
import feign.RequestLine;

public interface TodoApi {

    @RequestLine("GET /todos")
    List<Todo> findAll();

    @RequestLine("GET /todos/{todoId}")
    Todo getTodo(@Param("todoId") String todoId);

    @RequestLine("POST /todos")
    @Headers("Content-Type: application/json")
    Todo createTodo(Todo todo);

    @RequestLine("PUT /todos/{todoId}")
    @Headers("Content-Type: application/json")
    Todo updateTodo(@Param("todoId") String todoId, Todo todo);

    @RequestLine("DELETE /todos/{todoId}")
    void deleteTodo(@Param("todoId") String todoId);
}

4. FeignでAPIのインスタンスを生成して実行

APIのインターフェースは定義しましたが、その実装クラスは定義していません。APIのインスタンスはFeignで生成します。
APIの呼び出しはインターフェースのメソッドを実行するだけです。

TodoApiDemo.java
package com.example.feign.demo;

import java.util.List;

import feign.Feign;
import feign.Logger;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.okhttp.OkHttpClient;
import feign.slf4j.Slf4jLogger;

public class TodoApiDemo {

    public static void main(String[] args) {
        // 1. create instance of api interface with feign 
        TodoApi todoApi = Feign.builder()      // builder call at first
                .client(new OkHttpClient())    // use OkHttpClient of feign
                .encoder(new JacksonEncoder()) // use Jackson of feign
                .decoder(new JacksonDecoder()) // use Jackson of feign
                .logger(new Slf4jLogger())     // use Slf4j of feign
                .logLevel(Logger.Level.FULL)   // setting log level to most detail
                .target(TodoApi.class, 
                        "http://localhost:8090/todo-rest/api/v1");

        // 2. call api [GET /todos]
        List<Todo> todos = todoApi.findAll();
        System.out.println(todos);
    }
}

Builderパターンでインスタンスを生成するため、設定のための専用メソッドが用意されています。

  • clientメソッド
    • HTTPを発行する際に利用するクライアントを指定する
    • ApacheHttpClientOkHttpClient等がある
  • encoderメソッド
    • HTTPリクエストのBODYにデータを設定する際のコンバート処理(シリアライズ)で利用するオブジェクトを指定する
    • JacksonEncoderGsonEncoder等がある
  • decoderメソッド
    • HTTPレスポンスのBODYのデータをJavaのオブジェクトにコンバート処理(デシリアライズ)で利用するオブジェクトを指定する。
    • JacksonDecoderGsonDecoder等がある
  • loggerメソッド
    • ロガーを指定する
    • Feignはログ出力にSLF4Jを利用しているため、ログ出力の実装はSLF4Jに対応しているものであれば差し替え可能である。
  • logLevelメソッド
    • ログレベルを指定する
    • 最も詳細なログを出力したい場合、Logger.Level.FULLを指定する
  • targetメソッド
    • APIのインスタンス化に必要となる情報を指定する
    • 第1引数 : 3. APIのインターフェースを定義で定義したAPIのインターフェースのclass
    • 第2引数 : 実際にアクセスするAPIのエンドポイント(URL)

5. APIテストのサンプル(おまけ)

APIのインスタンスのメソッドを呼び出すだけなので、呼び出し方法に難しいことがありませんが、参考までにサンプルのAPIのテストを記載します。
Feignの使い方を目的としているため、テストの内容自体は適当です。

TodoApiTest.java
package com.example.feign.demo;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

import java.util.Date;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import feign.Feign;
import feign.Logger;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.okhttp.OkHttpClient;
import feign.slf4j.Slf4jLogger;

public class TodoApiTest {

    // test api
    TodoApi todoApi;

    private <T> T factory(Class<T> apiType, String url) {
        return Feign.builder()                 // builder call at first
                .client(new OkHttpClient())    // use OkHttpClient of feign
                .encoder(new JacksonEncoder()) // use Jackson of feign
                .decoder(new JacksonDecoder()) // use Jackson of feign
                .logger(new Slf4jLogger())     // use Slf4j
                .logLevel(Logger.Level.FULL)   // setting log level to most detail
                .target(apiType, url);
    }

    @Before
    public void setUp() throws Exception {
        todoApi = factory(TodoApi.class,
                "http://localhost:8090/todo-rest/api/v1");
    }

    @Test
    public void testFindAll() {
        List<Todo> todos = todoApi.findAll();
        System.out.println(todos);
        assertThat(todos.size(), is(2));
    }

    @Test
    public void testCreateTodo() {
        Todo todo = new Todo();
        todo.setTodoTitle("hello");
        Todo registeredTodo = todoApi.createTodo(todo);
        System.out.println(registeredTodo);
    }

    @Test
    public void testGetTodo() {
        String todoId = "36e06987-ef33-436a-a2c6-d215096ee902";
        Todo todo = todoApi.getTodo(todoId);
        System.out.println(todo);
        assertThat(todo.getTodoId(), is(todoId));
    }

    @Test
    public void testUpdateTodo() {
        String todoId = "36e06987-ef33-436a-a2c6-d215096ee902";
        String title = "update......";
        Todo todo = new Todo();
        todo.setTodoId(todoId);
        todo.setTodoTitle(title);
        todo.setCreatedAt(new Date());
        Todo updatedTodo = todoApi.updateTodo(todoId, todo);
        System.out.println(updatedTodo);
        assertThat(updatedTodo.getTodoId(), is(todoId));
        assertThat(updatedTodo.getTodoTitle(), is(title));
        assertThat(updatedTodo.isFinished(), is(true));
    }

    @Test
    public void testDeleteTodo() {
        String todoId = "36e06987-ef33-436a-a2c6-d215096ee902";
        todoApi.deleteTodo(todoId);
        System.out.println("ok");
    }
}

6. さいごに

今回はJavaのHTTPクライアント用ライブラリであるFeignについて説明しました。
ほぼAPIのインターフェースを定義するだけで実装が済んでしまうので、とても簡単に使えるライブラリではないでしょうか。
なお、今回は説明していませんがFeignJAXBJAX-RSにも対応しています。
テキストベースのHTTPリクエストしか扱えないという制約はありますが、その影響がないAPIでは積極的に利用していきたいと思いました。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5