5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

5
5
0

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
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?