Skip to content

PlaytikaOSS/feign-reactive

 
 

Repository files navigation

CircleCI codecov Codacy Badge Maven Central

feign-reactive

Use Feign with Spring WebFlux

Overview

Implementation of Feign on Spring WebClient. Brings you the best of two worlds together : concise syntax of Feign to write client side API on fast, asynchronous and non-blocking HTTP client of Spring WebClient.

Modules

feign-reactor-core : base classes and interfaces that should allow to implement alternative reactor Feign

feign-reactor-webclient : Spring WebClient based implementation of reactor Feign

feign-reactor-cloud : Spring Cloud implementation of reactor Feign (Ribbon/Hystrix)

feign-reactor-java11 : Java 11 HttpClient based implementation of reactor Feign (!! Winner of benchmarks !!)

feign-reactor-rx2 : Rx2 compatible implementation of reactor Feign (depends on feign-reactor-webclient)

feign-reactor-jetty : experimental Reactive Jetty client based implementation of reactor Feign (doesn't depend on feign-reactor-webclient). In future will allow to write pure Rx2 version.

  • have greater reactivity level then Spring WebClient. By default don't collect body to list instead starts sending request body as stream.
  • starts receiving reactive response before all reactive request body has been sent
  • process Flux<String> correctly in request and response body

feign-reactor-spring-cloud-starter : Single dependency to have reactive feign client operabable in your spring cloud application. Uses webclient as default client implementation.

feign-reactor-bom : Maven BOM module which simplifies dependency management for all reactive feign client modules.

Usage

Write Feign API as usual, but every method of interface

  • may accept org.reactivestreams.Publisher as body
  • must return reactor.core.publisher.Mono or reactor.core.publisher.Flux.
@Headers({ "Accept: application/json" })
public interface IcecreamServiceApi {

  @RequestLine("GET /icecream/flavors")
  Flux<Flavor> getAvailableFlavors();

  @RequestLine("GET /icecream/mixins")
  Flux<Mixin> getAvailableMixins();

  @RequestLine("POST /icecream/orders")
  @Headers("Content-Type: application/json")
  Mono<Bill> makeOrder(IceCreamOrder order);

  @RequestLine("GET /icecream/orders/{orderId}")
  Mono<IceCreamOrder> findOrder(@Param("orderId") int orderId);

  @RequestLine("POST /icecream/bills/pay")
  @Headers("Content-Type: application/json")
  Mono<Void> payBill(Publisher<Bill> bill);
}

Build the client :

/* Create instance of your API */
IcecreamServiceApi client = 
             WebReactiveFeign  //WebClient based reactive feign  
             //JettyReactiveFeign //Jetty http client based
             //Java11ReactiveFeign //Java 11 http client based
            .<IcecreamServiceApi>builder()
            .target(IcecreamServiceApi.class, "http://www.icecreame.com")

/* Execute nonblocking requests */
Flux<Flavor> flavors = icecreamApi.getAvailableFlavors();
Flux<Mixin> mixins = icecreamApi.getAvailableMixins();

or cloud aware client :

 IcecreamServiceApi client = CloudReactiveFeign.<IcecreamServiceApi>builder(WebReactiveFeign.builder())
            .setLoadBalancerCommandFactory(s -> LoadBalancerCommand.builder()
                    .withLoadBalancer(AbstractLoadBalancer.class.cast(getNamedLoadBalancer(serviceName)))
                    .withRetryHandler(new DefaultLoadBalancerRetryHandler(1, 1, true))
                    .build())
            .fallback(() -> Mono.just(new IcecreamServiceApi() {
                @Override
                public Mono<String> get() {
                    return Mono.just("fallback");
                }
            }))
            .target(IcecreamServiceApi.class,  "http://" + serviceName);

/* Execute nonblocking requests */
Flux<Flavor> flavors = icecreamApi.getAvailableFlavors();
Flux<Mixin> mixins = icecreamApi.getAvailableMixins();

Rx2 Usage

Write Feign API as usual, but every method of interface

  • may accept Flowable, Observable, Single or Maybe as body
  • must return Flowable, Observable, Single or Maybe.
@Headers({"Accept: application/json"})
public interface IcecreamServiceApi {

  @RequestLine("GET /icecream/flavors")
  Flowable<Flavor> getAvailableFlavors();

  @RequestLine("GET /icecream/mixins")
  Observable<Mixin> getAvailableMixins();

  @RequestLine("POST /icecream/orders")
  @Headers("Content-Type: application/json")
  Single<Bill> makeOrder(IceCreamOrder order);

  @RequestLine("GET /icecream/orders/{orderId}")
  Maybe<IceCreamOrder> findOrder(@Param("orderId") int orderId);

  @RequestLine("POST /icecream/bills/pay")
  @Headers("Content-Type: application/json")
  Single<Long> payBill(Bill bill);

Build the client :

/* Create instance of your API */
IcecreamServiceApi client = Rx2ReactiveFeign
    .builder()
    .target(IcecreamServiceApi.class, "http://www.icecreame.com")

/* Execute nonblocking requests */
Flowable<Flavor> flavors = icecreamApi.getAvailableFlavors();
Observable<Mixin> mixins = icecreamApi.getAvailableMixins();

Header to request

There are 2 options:

ReactiveHttpRequestInterceptor

ReactiveFeignBuilder
    .addRequestInterceptor(ReactiveHttpRequestInterceptors.addHeader("Cache-Control", "no-cache"))
    .addRequestInterceptor(request -> Mono
            .subscriberContext()
            .map(ctx -> ctx
                    .<String>getOrEmpty("authToken")
                    .map(authToken -> {
                      MultiValueMapUtils.addOrdered(request.headers(), "Authorization", authToken);
                      return request;
                    })
                    .orElse(request)));

@RequestHeader parameter

You can use @RequestHeader annotation for specific parameter to pass one header or map of headers @RequestHeader example

Spring Auto-Configuration

You can enable auto-configuration of reactive Feign clients as Spring beans just by adding feign-reactor-spring-configuration module to classpath. Spring Auto-Configuration module Sample cloud auto-configuration project with Eureka/WebFlux/ReaciveFeign

License

Library distributed under Apache License Version 2.0.