1. 概要

このチュートリアルでは、Netflixによって開発された宣言型HTTPクライアントであるFeignを紹介します。

Feignは、HTTPAPIクライアントを簡素化することを目的としています。 簡単に言えば、開発者は、実際の実装が実行時にプロビジョニングされている間、インターフェースを宣言して注釈を付けるだけで済みます。

2. 例

このチュートリアルでは、RESTAPIエンドポイントを公開する書店アプリケーションの例を使用します。

プロジェクトのクローンを簡単に作成して、ローカルで実行できます。

mvn install spring-boot:run

3. 設定

まず、必要な依存関係を追加しましょう。

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>10.11</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-gson</artifactId>
    <version>10.11</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-slf4j</artifactId>
    <version>10.11</version>
</dependency>

feign-core 依存関係(これもプルインされます)に加えて、いくつかのプラグインを使用します。特に、SquareのOkHttpを内部的に使用するためのfeign-okhttpです。リクエストを行うクライアント。GoogleのGSONをJSONプロセッサとして使用する場合はfeign-gson、リクエストをログに記録するために Simple LoggingFacadeを使用する場合はfeign-slf4j

実際にログ出力を取得するには、クラスパスにSLF4Jでサポートされているお気に入りのロガー実装が必要です。

クライアントインターフェイスの作成に進む前に、まずデータを保持するためのBookモデルを設定します。

public class Book {
    private String isbn;
    private String author;
    private String title;
    private String synopsis;
    private String language;

    // standard constructor, getters and setters
}

注:JSONプロセッサーには少なくとも「引数なしのコンストラクター」が必要です。

実際、RESTプロバイダーはハイパーメディア駆動型API であるため、さらに単純なラッパークラスが必要になります。

public class BookResource {
    private Book book;

    // standard constructor, getters and setters
}

注: We は、 BookResource をシンプルに保ちます。これは、サンプルのFeignクライアントがハイパーメディア機能の恩恵を受けていないためです。

4. サーバ側

Feignクライアントを定義する方法を理解するために、まず、RESTプロバイダーでサポートされているメソッドと応答のいくつかを調べます。

簡単なcurlshellコマンドを使用して、すべての本を一覧表示してみましょう。 すべての呼び出しの前に/api を付けることを忘れないでください。これは、アプリケーションのサーブレットコンテキストです。

curl http://localhost:8081/api/books

その結果、JSONとして表される完全な本のリポジトリを取得します。

[
  {
    "book": {
      "isbn": "1447264533",
      "author": "Margaret Mitchell",
      "title": "Gone with the Wind",
      "synopsis": null,
      "language": null
    },
    "links": [
      {
        "rel": "self",
        "href": "http://localhost:8081/api/books/1447264533"
      }
    ]
  },

  ...

  {
    "book": {
      "isbn": "0451524934",
      "author": "George Orwell",
      "title": "1984",
      "synopsis": null,
      "language": null
    },
    "links": [
      {
        "rel": "self",
        "href": "http://localhost:8081/api/books/0451524934"
      }
    ]
  }
]

getリクエストにISBNを追加することで、個々のBookリソースをクエリすることもできます。

curl http://localhost:8081/api/books/1447264533

5. 偽のクライアント

最後に、Feignクライアントを定義しましょう。

@RequestLine アノテーションを使用して、HTTP動詞とパス部分を引数として指定します。 パラメータは、@Paramアノテーションを使用してモデル化されます。

public interface BookClient {
    @RequestLine("GET /{isbn}")
    BookResource findByIsbn(@Param("isbn") String isbn);

    @RequestLine("GET")
    List<BookResource> findAll();

    @RequestLine("POST")
    @Headers("Content-Type: application/json")
    void create(Book book);
}

注:偽のクライアントは、テキストベースのHTTP APIのみを使用できます。つまり、バイナリデータを処理できません。 ファイルのアップロードまたはダウンロード。

これですべてです!次に、 Feign.builder()を使用して、インターフェイスベースのクライアントを構成します。 実際の実装は実行時にプロビジョニングされます。

BookClient bookClient = Feign.builder()
  .client(new OkHttpClient())
  .encoder(new GsonEncoder())
  .decoder(new GsonDecoder())
  .logger(new Slf4jLogger(BookClient.class))
  .logLevel(Logger.Level.FULL)
  .target(BookClient.class, "http://localhost:8081/api/books");

Feignは、JSON / XMLエンコーダーやデコーダー、またはリクエストを行うための基盤となるHTTPクライアントなどのさまざまなプラグインをサポートしています。

6. 単体テスト

クライアントをテストするための3つのテストケースを作成しましょう。 org.hamcrest.CoreMatchers。*およびorg.junit.Assert。*には静的インポートを使用することに注意してください。

@Test
public void givenBookClient_shouldRunSuccessfully() throws Exception {
   List<Book> books = bookClient.findAll().stream()
     .map(BookResource::getBook)
     .collect(Collectors.toList());

   assertTrue(books.size() > 2);
}

@Test
public void givenBookClient_shouldFindOneBook() throws Exception {
    Book book = bookClient.findByIsbn("0151072558").getBook();
    assertThat(book.getAuthor(), containsString("Orwell"));
}

@Test
public void givenBookClient_shouldPostBook() throws Exception {
    String isbn = UUID.randomUUID().toString();
    Book book = new Book(isbn, "Me", "It's me!", null, null);
    bookClient.create(book);
    book = bookClient.findByIsbn(isbn).getBook();

    assertThat(book.getAuthor(), is("Me"));
}

7. 参考文献

サービスが利用できない場合に何らかのフォールバックが必要な場合は、 HystrixFeign をクラスパスに追加し、 HystrixFeign.builder()を使用してクライアントを構築できます。

Hystrixの詳細については、この専用チュートリアルシリーズをご覧ください。

さらに、Spring Cloud Netflix HystrixをFeignと統合したい場合は、こちらに専用の記事があります。

さらに、クライアント側の負荷分散やサービス検出をクライアントに追加することもできます。

これは、 Ribbon をクラスパスに追加し、次のようにビルダーを使用することで実現できます。

BookClient bookClient = Feign.builder()
  .client(RibbonClient.create())
  .target(BookClient.class, "http://localhost:8081/api/books");

サービスディスカバリでは、Spring CloudNetflixEurekaを有効にしてサービスを構築する必要があります。 次に、Spring CloudNetflixFeignと統合するだけです。 その結果、リボンの負荷分散を無料で利用できます。 これについての詳細はここで見つけることができます。

8. 結論

この記事では、Feignを使用して宣言型HTTPクライアントを構築し、テキストベースのAPIを使用する方法について説明しました。

いつものように、このチュートリアルに示されているすべてのコードサンプルはGitHubで入手できます。