[JAVA] Feign, which implements an API client with just an interface, is very convenient!

1. What is Feign?

Feign is a very simple Java library for HTTP clients. Evaluation of: star: 2745 on github (as of April 2, 2018), so it seems to be a fairly used library. You can make an HTTP request with very short code. The flow when using Feign is as follows.

The disadvantages are that only text-based HTTP requests can be issued, and HTTP responses can only access BODY.

(Added on 2018/4/8) The default of Feign is not supported, but you can handle binary HTTP requests with another module or your own implementation.

2. Preparation of library

Fegin is configured so that the components used can be replaced. This time, we will use ʻOkHttp, Jackson, and Logback`.

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. Define API interface

Define the API you want to call as an interface.

This time, I would like to consider the case of calling the API shown below.

Item number path HTTP method Description
1 /todos GET Get all TODO
2 /todos/{todoId} GET Gets the TODO specified by todoId
3 /todos POST Create a TODO with the sent data
The content type isapplication/jsonTo
4 /todos/{todoId} PUT Update the TODO specified by todoId
The content type isapplication/jsonTo
5 /todos/{todoId} DELETE Delete the TODO specified by todoId

The API endpoint (URL) is 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. Create and execute API instance with Feign

I have defined the API interface, but not its implementation class. An instance of the API is created with Feign. Calling the API simply executes the methods of the interface.

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);
    }
}

A dedicated method for configuration is provided to create an instance with the Builder pattern.

5. API test sample (bonus)

It's not difficult to call it because it only calls the method of the API instance, but for reference, I will describe the test of the sample API. Since the purpose is to use Feign, the content of the test itself is appropriate.

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. Finally

This time, I explained about the Java HTTP client library Feign. It's a very easy-to-use library because it's almost done by defining the API interface. Although not explained this time, Feign also supports JAXB and JAX-RS. There is a restriction that only text-based HTTP requests can be handled, but I wanted to actively use it with APIs that are not affected by it.

Recommended Posts

Feign, which implements an API client with just an interface, is very convenient!
Implement API client with only annotations using Feign (OpenFeign)
What is an interface?
[swift5] Try to make an API client with various methods
What exactly is an API?