Creating declarative HTTP clients with Feign
There are many reasons why microservices-based architecture gets more and more attention, like separating domains, maintainability, cloud readiness, testability etc. There are also some challenges related to this way of creating software, such as communication between distributed microservices. One of the most popular ways to let them interact with each other is still the good, old and simple HTTP call. In this case we need to choose how to do it. There are a bunch of options here – starting from the old plain HttpUrlConnection, over ApacheHttpClient, the refreshed core HttpClient, OkHttp, Spring’s RestTemplate or the newer async WebClient – usually the newer the solution, the more handy it is to use by hiding and automating some boilerplate code.
Imperative vs. declarative
Still, all the clients mentioned above are imperative clients, so we have to define HOW our request should be done. Let’s look at the example (OkHttp):
public class UsersAPI {
// (...)
public List<User> getUsers() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final OkHttpClient client = new OkHttpClient();
final Request request = new Request.Builder()
.url("http://localhost:8080/")
.get()
.build();
final TypeReference<List<User>> collectionType = new TypeReference<List<User>>() {};
try (Response response = client.newCall(request).execute()) {
return response.body() != null ? mapper.readValue(response.body().byteStream(), collectionType) : null;
}
}
public User getUser(final Long userId) {
// ...
}
// (...)
}
}
It looks clearly like a boilerplate code which will be quite similar in other CRUD operations. To be DRY we could certainly find the common part and extract it, but it would still be a bit cumbersome. But, how about this:
public interface UsersAPI {
// (...)
@RequestLine("GET")
List<User> getUsers();
@RequestLine("GET /{userId}")
User getUser(@Param("userId") final Long userId);
// (...)
}
In this case we just define WHAT should be done – it is the declarative code, like a JPA repository in Spring.
Feign
Ok, hold on, it’s nice, but it is only an interface, we still need to write the implementation! Since this interface is well annotated we don’t have to write it ourselves. To have our client ready to work with, we simply need to let Feign create the implementation for us:
final UsersAPI usersAPIClient = Feign.builder()
.client(new OkHttpClient())
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.logger(new Slf4jLogger(UsersRestClient.class))
.target(User.class, "http://localhost:8080");
To be able to do this, we must get some dependencies:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
</dependency>
As you can see, we are telling Feign what to use to create the implementation for us. In this example, under the hood, Feign will use the same OkHttp as we used manually before, it will also decode response body using Jackson. Then, the created implementation is ready to use just by:
final List<User> allUsers = usersAPIClient.getUsers();
As you see, we can choose what should be used by Feign as an HTTP client, a body encoder/decoder or even a logger, but we need to remember about having necessary modules on the classpath. Additionally, we can even write corresponding implementations by ourselves – Feign is very flexible in this aspect.
What about Spring?
Feign is even easier to use with Spring, because its Spring Cloud’s version adds bean autoconfiguration and support for standard Spring MVC annotations. To take advantage of these facilities, we need to add a dependency on Spring Cloud OpenFeign:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Then our client interface would look like this:
@FeignClient(name = "user-server", url = "${user-server.url:http://localhost:8080}")
public interface UsersAPI {
// (...)
@GetMapping
List<User> getUsers();
@GetMapping("/{userId}")
User getUser(@PathVariable("userId") Long userId);
// (...)
}
We also need to remember about enabling OpenFeign support in our Spring application:
@SpringBootApplication
@EnableFeignClients(basePackages = "pl.jlabs.example.feign.client")
public class UserThinWebApplication {
public static void main(String[] args) {
SpringApplication.run(UserThinWebApplication.class, args);
}
}
While we are using standard Spring MVC annotations, it’s really convenient to use the @FeignClient annotated interface as a common interface for both the server-side controller and the client autogenerated by OpenFeign to ensure their uniformity.
OpenFeign customizations
To be as concise and easy to use as possible, OpenFeign in Spring applications creates clients using some default beans, but there is of course the possibility to customize them. OpenFeign is so flexible and there are so many possible ways to adjust its behaviour that even trying to mention them all would go outside the scope of this short overview – OpenFeign documentation is a comprehensive source of such kind of information.
Can it be asynchronous?
Currently (as of April 2023), Feign supports only synchronous http calls, so when you would like to leverage benefits of asynchronicity, you need to use something else (or manually wrap it into something like CompletableFuture, which will turn out to be less concise). Fortunately, there are alternatives – you can use i.e. ReactiveFeign or the brand new Spring 6’s @HttpExchange. Let’s take a quick look at Reactive Feign, which is very similar to its synchronous predecessor. Get the dependencies first:
<dependency>
<groupId>com.playtika.reactivefeign</groupId>
<artifactId>feign-reactor-spring-cloud-starter</artifactId>
</dependency>
And then our client will look like this:
@ReactiveFeignClient(name = "user-server", url = "${user-server.url:http://localhost:8080}")
public interface UsersAPI {
// (...)
@GetMapping
Flux<User> getUsers();
@GetMapping("/{userId}")
Mono<User> getUser(@PathVariable("userId") final Long userId);
// (...)
}
We also need to remember about enabling Reactive Feign in the Spring application:
@SpringBootApplication
@EnableReactiveFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
And we are ready to use a declarative asynchronous client!
Integrations
Spring Cloud OpenFeign integrates very well with other Spring Cloud components like CircuitBreaker, ServiceDiscovery or LoadBalancer. More details about possible integrations can be found in the Spring Cloud documentation.
Summary
With declarative programming we are able to abstract away repetitive, boilerplate implementations and, as a result, have a cleaner and more concise codebase – simply easier for development and maintenance. In case of database operations, Spring Data JPA has been there for years, and it has already proved its effectiveness. In my humble opinion, declarative HTTP clients can go the same way – even Spring contributors have introduced the declarative @HttpExchange along with Spring 6. OpenFeign is a mature and well documented implementation of declarative HTTP client, and I think it’s worth to give it a try in your current and future projects.
Complete sample using OpenFeign can be found on Github.