Spring Cloud: Designing Feign client

Introduction

I would like to gather here some thoughts and practical experience on designing and using Netflix Feign within Spring Cloud applications, something that you won’t be able to find in the Spring reference, although if you haven’t hear about Feign and Spring Netflix Cloud yet I highly encourage you to read the Spring Netflix Cloud reference first or see the Feigh github page.

First of all let me say that I am generally impressed by the capabilities of Feign library and it’s flexibility. It has quite recently included support for introspecting the base interfaces so it’s possible to group the common operations like for instance CRUD methods in common base interface or to share the same interface both for the Spring MVC ‘Controller’ and the Feign client.

Feign seamlessly integrates with other Netflix projects through Spring Cloud integration layer like Eureka for service discovery and Ribbon for load balancing. Also Zuul is capable of autodiscovering you Feign clients and registering them as routes.

Base example

The base example limits to define an interface with @RequestMapping annotated methods that itself has a @FeignClient annotation.

@FeignClient(Authorization.SERVICE_ID)
public interface AccountClient {

    @RequestMapping(value = "/account",
            method = RequestMethod.GET,
            produces = {
                    MediaType.APPLICATION_JSON_VALUE
            })
    ResponseEntity<Resource<UserInfoDTO>> getAccount();
}

Above we specify only the service id, thanks to that we will facilitate the autodiscovery through Eureka and loadbalancing using Ribbon.

Starting from Feign 8.6 you can define a base interface for your Feign clients, and thanks to that you can extract common contract that can be used both for client and for the controller implementation for instance.

public interface AccountResource {

    @RequestMapping(value = "/account",
            method = RequestMethod.GET,
            produces = {
                    MediaType.APPLICATION_JSON_VALUE
            })
    ResponseEntity<Resource<UserInfoDTO>> getAccount();
}

@FeignClient(Authorization.SERVICE_ID)
public interface AccountClient extends AccountResource {
}

@RestController
public class AccountResourceImpl extends BaseResource implements AccountResource {

    @Override
    public ResponseEntity<Resource<UserInfoDTO>> getAccount() {
         ...
    }
}

In above example we define our custom contract in a form of base interface, that it is used for the Feign client. The same interface is also being implemented by the controller. You could of course omit that, and implement directly the AccountClient in the controller, but I personally do not like the idea of polluting your controller with additional annotations.

Spring Netflix Cloud 1.0.3.RELEASE defines the Feign dependency in version 8.1.1, so in order to be able to run above example you will need to add Feign 8.6 instead. In Gradle this is simple as adding custom resolution strategy:

configurations.all {
    resolutionStrategy {
        eachDependency { DependencyResolveDetails details ->
            if (details.requested.group == 'com.netflix.feign') {
                details.useVersion "8.6.0"
            }
        }
    }
}

Generic types

Feign 8.6+ will let you to define you client in a form of generic iterface. So bellow is completely correct Feign base interface:

public interface CrudClient<T> {

    

    @RequestMapping(method = RequestMethod.POST, value = "/")

    long save(T entity);

    

    @RequestMapping(method = RequestMethod.PUT, value = "/{id}")
    
    void update(@PathVariable("id") long id, T entity);

    

    @RequestMapping(method = RequestMethod.GET, value = "/{id}")
    
    T get(@PathVariable("id") long id);

   

    @RequestMapping(method = RequestMethod.DELETE, value = "/{id}")
    
    void delete(@PathVariable("id") long id);

}

There are some additional constraints when defining your base contracts. Although this feature might not be so much useful in Spring Netflix Cloud yet due to:

Type level @RequestMapping annotation

All @FeignClient classes are being registered as Spring beans and this has also interesting consequences. Whenever you have decided to annotate your interface with @RequestMapping annotation on type level. That interface will be also registered as Spring MVC controller. There is bug issued for this behavior.

Design Considerations

The first decision that you would like to make when designing you application is whether you want to keep the Feign clients independently from you server implementation or not . Prior Feign 8.6 your options were simple, you had to develop the client interface apart from the server side code. Sharing the same contract for both of them was still possible though, if you have used your annotated @FeignClient as a base interface for your controllers. This will have huge impact in the future, because you have to be aware about the constraints that Feign will impose.

MIME/Media type mapping

At moment of writing this post Spring Cloud together with Feign only allows to specify single media type for the method mapping and this applies both to the RequestMapping#cosumes attribute as also to RequestMapping#produces. This seem to make sense since on the client side there is no functionality allowing to specify the Accept or Content-Type header values per client invocation, although I might imagine having this constraint relaxed and allow to specify globally those headers through RequestInterceptors.

When you specify more then one media type you will see fallowing exception message:

Error creating bean with name 'accountClient': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Method getAccount can only contain at most 1 consumes field. Found: [application/xml, application/json]

Response HTTP Headers

You will be able to access the response HTTP headers whenever you will return the ResponseEntity from the client code.

@RequestMapping(value = "/account",
        method = RequestMethod.GET,
        produces = {
                MediaType.APPLICATION_JSON_VALUE
        })
ResponseEntity<UserInfoDTO> getAccount();

HATEOAS

Remarkably HATEOAS links can be also consumed through Feign by wrapping your reponse into Spring HATEOAS Resource type. Afterward you can access those by simple calling response.getBody().getLinks()

@RequestMapping(value = "/account",
      method = RequestMethod.GET,
      produces = {
             MediaType.APPLICATION_JSON_VALUE
      })
ResponseEntity<Resource<UserInfoDTO> getAccount();

Providing that you will populate those in your web application.

Null arguments handling

Feign disallows to pass null as method invocation argument.

Optional parameters

If you contract specifies an optional parameter with the respect of previous point, your only option to omit them is to pass there default values. Unfortunately you are unable to map multiple methods to the same URL address as workaround (one with optional parameters and second without them) ending with java.lang.IllegalStateException: Ambiguous mapping exception.

Exceptions

By default Feign will trow FeignException whenever it recieves the 4xx-5xx error status code. The exception by default will populate the message with serialized response so whenever you will return for instance a JSON error payload you will end in something like this (this is single longish string):

“feign.FeignException: status 500 reading AccountClient#getAccount(); content:
{“timestamp”:1439806103425,”status”:500,”error”:”Internal Server Error”,”exception”:”java.lang.RuntimeException”,”message”:”Request processing failed; nested exception is java.lang.RuntimeException”,”path”:”/account”}”

If that is unsatisfying for your use case you can provide a custom ErrorDocoder as a Spring bean, then FeignClientFactoryBean will happily capture and register it for you. You may also want to try out the custom vnd.error decoder: https://github.com/jmnarloch/feign-vnderror-spring-cloud-starter. Vnd.error itself is effort for unifying the JSON error response representations.

File uploads

This is one of the use cases that I completely fail to accomplish, also I wasn’t able to find some working examples on the web.

File dowloads

In the contrary file download is no much of deal you can simply read the whole file and return it as byte array.

Async processing

Feign Client does not support yet async request processing, so if you have tied your client and server with the same contract you won’t be able to return Spring’s Callable, DeferredResult or ListenableFuture. If you plan to use them in your server side implementation for better scalability you can’t tie up your server code with the same contract.

Server side specific types

This is another reason why you would like to keep the client contract seperated from the server side code. Whenever you would like to have access the JEE Servlet specific types like HttpServletRequest, Session or for instance Principal on the server side, you won’t be able to provide them on the client side. Another aspect is that, most likely you won’t like to “pollute” the client code with those parameters especially that from client perspective they might be meaningless and unnecessary confusing.

Request Interceptors

You can implement custom behavior and additional request processing by implementing the Feign RequestInterceptor and using it for example to alter the request headers.

Accept-Encoding interceptor

One of such good example is to create the custom RequestInterceptor that would allow for receiving compressed responses, though you should better wait for Spring Cloud Netflix 1.1.0 being released.

OAuth2 Authentication

Another very useful example of defining custom RequestInterceptor is to propagate the OAuth  authorization token with your Feign client.

Summary

At this point it looks like deciding to keep you client code separated from the server side give you far more possibilities and does not come with so many constraint as when you decide to use the same base interface for your server side implementation. Although this come with the price that you would have to cover the client code with integration tests that would gave you guarantee that at any point in time they still work with your application.

 

5 comments

  1. Anton Konovchenko · September 1, 2015

    Very helpful, thanks a lot!

    Like

  2. Pingback: Spring Cloud: Feign Vnd.error decoder | I just do things
  3. Pengcheng Lee · March 23, 2016

    Thanks a lot

    Like

  4. I also stumbled upon the file upload problem also and just wrote a small howto.
    We are using Base64 to encode and then upload the encoded content wrapped within a JSON object… say nothing, it’s not as slowly as one might expect 🙂
    see: https://pingunaut.com/blog/file-uploads-with-spring-cloud-feign/
    Thanks for your article. Great Introduction!

    Like

  5. Martin Spielmann (@pingunaut) · August 1, 2016

    I also faced the file upload problem some time ago. The solution we created uses simple Base64 encoding to transfer the file data. I created a small blog post to describe it: https://pingunaut.com/blog/file-uploads-with-spring-cloud-feign/
    Thanks for this great inroduction!

    Like

Leave a comment