Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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では積極的に利用していきたいと思いました。

5zm
都内のIT企業でアーキテクトのお仕事をしてます。そのご縁で「Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発」を共著させて頂きました。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした