Client REST en Java: trop facile avec Feign !

Igor Laborie

Java REST client with Feign

whoami

Igor Laborie

Expert Java & Web, MonkeyPatch
@ilaborie
igor@monkeypatch.io

Roadmap

  1. Basic
    • CRUD operations, JSON
  2. Advanced
    • Async with RxJava
    • Circuit Breaker with Hystrix
    • HTTP/2 with Java 9

Basic

whois feign

https://github.com/OpenFeign/feign

Open Source: license Apache 2.0

A Netflix Project

Java 7+, Java 8 friendly

Not opinionated

du -h target/*.jar

Classes Fat jar
Jersey (JAX-RS 2) 112* 4,6 Mo
Retrofit 2 38 740 Ko
Feign 34 336 Ko

*: only into javax.ws.rs

See code on github

Most Java applications require a persistent class representing felines.

Hibernate Documentation ...

Most Java applications require a microservice for felines.

Any buzzword addicts

curl http://cats/swagger

Swagger Documentation

Run with: docker run -p 8080:8080 ilaborie/cats-microservice
See code on github

Live coding

CRUD operations, JSON

See code on github

Maven dependencies

Current lastest release: 9.3.1

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-core</artifactId>
  <version>${feign.version}</version>
</dependency>
<dependency>
  <groupId>io.github.openfeign</>roupId>
  <artifactId>feign-gson</artifactId>
  <version>${feign.version}</version>
</dependency>
<dependency>
  <groupId>io.github.openfeign</>roupId>
  <artifactId>feign-slf4j</artifactId>
  <version>${feign.version}</version>
</dependency>

Interface

import feign.*;
import java.util.List;
 
@Headers("Content-Type: application/json")
public interface CatClient {
  @RequestLine("GET /")
  List<Cat> findAll();
 
  @RequestLine("GET /{id}")
  Cat findById(@Param("id") String id);
 
  @RequestLine("POST /")
  Cat create(Cat newCat);
 
  @RequestLine("DELETE /{id}")
  Cat delete(@Param("id") String id);
}

Feign Builder

CatClient client = Feign.builder()
  // Create request body
  .encoder(new GsonEncoder())
  // Transform response body
  .decoder(new GsonDecoder())
  // Add custom header
  .requestInterceptor(template -> template.header("Date", Instant.now().toString()))
  // Tips: debug lite
  .requestInterceptor(System.out::println)
  // Tips: debug with logger
  .logLevel(Logger.Level.FULL)
  .logger(new Slf4jLogger())
  .target(CatClient.class, url);

Encoders & Decoders

Under the hood

No White/Dark Magic
The java.lang.reflect.Proxy allow you to dynamically implement the interface.

Use the java.net.HttpURLConnection to send and process HTTP requests. (by default)

Use the reflection API to extract meta-data.
If you prefer JAX-RS annotation you can use feign-jaxrs.

Advanced

How to be async ?

Do not write a custom feign.Decoder!

You can use Java 8 CompletableFuture

Or the feign-hystrix provide RxJava support

Live coding

Async with ReactiveX

See code on github

Circuit Breaker

By Bidgee (Own work) CC BY-SA 3.0, via Wikimedia Commons

Live coding

Circuit Breaker with Hystrix

See code on github

With Hystrix

// Fallback implementation
CatClient fallback = () -> {
    Cat cat = new Cat("Chuck Norris's cat", CatRace.Sphynx);
    return Observable.just(Collections.singletonList(cat));
};
 
// Get Client with CircuitBreaker
CatClient client = HystrixFeign.builder()
    // ...
    .target(CatClient.class, url, fallback);
 
// Find All every 2 seconds
Observable.interval(2, SECONDS)
    .flatMap(i -> client.findAll()) // to Observable<List<Cat>>
    .flatMap(Observable::from) // unfold to Observable<Cat>
    .forEach(System.out::println);

Incoming train ....

Download Java 9 Early Access
Can be downloaded with/without Jigsaw

Or use docker:

docker run openjdk:9 java -version

docker run maven:3-jdk-9 mvn -version

JEP 110: HTTP/2 Client

java.net.http

URI uri = new URI("http://127.0.0.1:8080/hello")
HttpResponse resp = HttpRequest.create(uri)
    .GET().response();
int statusCode = resp.statusCode();
String body = resp.body(HttpResponse.asString());
System.out.printf("[%s] %s\n", statusCode, body);

Live coding

Custom feign.Client with Java 9

See code on github

Custom Client with Java 9

HttpRequest.Builder builder = HttpRequest.create(new URI(request.url()));
// Handle body
byte[] bytes = request.body();
boolean isBodyEmpty = bytes == null || bytes.length == 0;
builder = builder.body(isBodyEmpty?noBody():fromByteArray(bytes));
// Apply headers
for (Map.Entry<String, Collection&tl;String>> entry : request.headers().entrySet()) {
    String values = entry.getValue().stream().collect(Collectors.joining(", "));
    builder = builder.setHeader(entry.getKey(), values);
}
// Execute request
HttpResponse response = builder.method(request.method()).response();
 
// Build Feign response
return Response.builder()
    .status(response.statusCode())
    .headers(mapList2MapCollection(response.headers().map()))
    .body(response.body(HttpResponse.asByteArray()))
    .build();
  

feign.Client implementation

So why feign:

Open & active community

Simple & intuitive

Extensible, useful plugins, easy to write your own

Not opinionated

Question

Any Question ?

http://bit.ly/devfest-feign