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

## 1. Feignとは

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

* [1. APIのインターフェースを定義](#3-apiのインターフェースを定義)
* [2. FeignでAPIのインスタンスを生成して実行](#4-feignでapiのインスタンスを生成して実行)

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

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

* [Feignに認証ありのProxyを設定する方法](https://qiita.com/5zm/items/e9a78d73836d8692fae7)
* [Feignでファイルダウンロードを実現する方法](https://qiita.com/5zm/items/940195d776497f70f683)
* [Feignでファイルアップロードを実現する方法](https://qiita.com/5zm/items/ed35fec426b0a04aab49)

## 2. ライブラリの用意

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

```xml: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を作成する<br>コンテントタイプは`application/json`とする |
| 4 | /todos/{todoId} | PUT | todoIdで指定されたTODOを更新する<br>コンテントタイプは`application/json`とする |
| 5 | /todos/{todoId} |  DELETE | todoIdで指定されたTODOを削除する |

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

```java: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の呼び出しはインターフェースのメソッドを実行するだけです。

```java: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を発行する際に利用するクライアントを指定する
 * `ApacheHttpClient`、`OkHttpClient`等がある 
* `encoder`メソッド
 * HTTPリクエストのBODYにデータを設定する際のコンバート処理（シリアライズ）で利用するオブジェクトを指定する
 * `JacksonEncoder`、`GsonEncoder`等がある
* `decoder`メソッド
 * HTTPレスポンスのBODYのデータをJavaのオブジェクトにコンバート処理（デシリアライズ）で利用するオブジェクトを指定する。
 * `JacksonDecoder`、`GsonDecoder`等がある
* `logger`メソッド
 * ロガーを指定する
 * `Feign`はログ出力に`SLF4J`を利用しているため、ログ出力の実装は`SLF4J`に対応しているものであれば差し替え可能である。
* `logLevel`メソッド
 * ログレベルを指定する
 * 最も詳細なログを出力したい場合、`Logger.Level.FULL`を指定する
* `target`メソッド
 * APIのインスタンス化に必要となる情報を指定する
  * 第１引数　：　[3. APIのインターフェースを定義](#3-apiのインターフェースを定義)で定義したAPIのインターフェースの`class`
  * 第２引数　：　実際にアクセスするAPIのエンドポイント（URL）

## 5. APIテストのサンプル（おまけ）

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

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