Architecture Réactive

L’architecture réactive est un sujet que je n’ai découvert que très récemment et qui, avec l’arrivée de Spring 5, a éveillé ma curiosité.

Dans le monde de Java vous connaissez sans doute Akka ou RxJava, qui sont des libraires implémentant la spécification Reactive Streams adoptée dans Java 9 en tant que java.util.concurrent.Flow.

Reactor est un projet récent et facilement implémentable dans un projet Spring, dû à son haut niveau d’abstraction. Il repose sur l’API de Java 8 avec l’exploitation de java.time.Duration, java.util.stream.Stream et java.util.concurrent.CompletableFuture<T>.

Qu’est-ce qu’une architecture réactive ?

Pour faire court, en une image.

Issu du Reactive Manifesto

Issu du Reactive Manifesto


Responsive: Votre application se doit de pouvoir répondre en un minimum de temps et de détecter et résoudre les problèmes rapidement.

J’aime comment Kevin Webber illustre ce principe. Imaginez que vous voulez vous faire un café mais qu’il vous manque de la crème et du sucre.

Une approche possible :

  • Mettre du café dans la machine.
  • Aller faire les courses pendant que la machine fait couler le café.
  • Acheter de la crème et du sucre.
  • Retournez chez soi.
  • Boire le café instantanément.
  • Profiter de la vie.

Et en voici une autre :

  • Aller faire les courses.
  • Acheter de la crème et du sucre.
  • Retournez chez soi.
  • Mettre du café dans la machine.
  • Regarder impatiemment la cafetière se remplir.
  • Expérimenter le manque de caféine.
  • S’écrouler à cause du manque (oui c’est l’extrême, mais ce n’est que pour illustrer!).

La première approche illustre bien la frontière qui confronte l’espace et le temps de ces deux scénarios, voici donc en quoi consiste le terme “responsive” (réactivité en français).

Elastic: Être en capacité de pouvoir réagir à une charge du système en allouant plus ou moins de ressources. Si votre application devient populaire, il faut pouvoir encaisser une charge plus importante et être en mesure de répondre à la résilience de votre système et donc aussi à sa réactivité.

Message Driven: Chaque action effectuée est transmise de façon asynchrone aux composants de l’application.

Resilient: L’application doit être en capacité de surmonter les potentielles erreurs.

Encore une fois, je vous renvoie à l’article anglophone de Kevin Webber qui explique en détail ces quatre points.


D’après le manifeste, ces changements s’opèrent avec le tournant qu’ont pris les besoins des applications durant ces dernières années.

Seulement quelques années auparavant, une grosse application devait avoir une dizaine de serveurs, quelques secondes de temps de réponse, des heures de maintenance hors connexion et des gigaoctets de données.

Aujourd’hui les applications sont déployées sur toutes sortes de plateformes : des smartphones aux clusters cloud avec des milliers de processeurs multi-core.

Les utilisateurs s’attendent maintenant à un temps de réponse dans la milliseconde et 100 % de disponibilité. Les données se mesurent en petaoctets.

D’où la nécessité d’avoir une application flexible, faiblement couplée et scalable. Ce système est plus résilient aux échecs, c’est-à-dire que lorsqu’une erreur intervient, tout doit se faire avec élégance et non par une cascade d’erreurs incompréhensibles pour le client.

Ce n’est donc pas une nouvelle technologie, mais bien un paradigme d’architecture logicielle pour pallier aux problèmes actuels.

Spring WebFlux

Afin de vous montrer comment construire une API réactive en quelques étapes, voici les modules nécessaires:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Pourquoi Spring WebFlux et pas Spring MVC ?

Spring WebFlux est l’équivalent de Spring MVC, mais avec l’implémentation des APIs asynchrones en plus. Si une nouvelle fonctionnalité est ajoutée à MVC, elle le sera aussi dans WebFlux et réciproquement.

WebFlux peut fonctionner dans des containers de servlets supportant l’API IO non-bloquante Servlet 3.1 tels que Netty ou Undertow.

Revenons à nos moutons.

Prenons l’exemple de transactions financières. Nous voulons pouvoir créer une application réactive avec la récupération. Nous exposons donc une API REST simplifiée pour notre cas.

Notre JavaBean Transaction

public class Transaction {

  private UUID id;
  private float amount;
  private Date date;

  public Transaction(float amount) {
    this.amount = amount;
    this.date = new Date();
    this.id = UUID.randomUUID();
  }

  public UUID getId() {
    return id;
  }

  public float getAmount() {
    return amount;
  }

  public Date getDate() {
    return date;
  }

}

Pour l’exemple nous passons par un stockage direct en mémoire au lieu d’une base de données, certes je vous déconseille de faire de la sorte dans vos applications, surtout en utilisant des java.util.List .

Gestion de stockage in-memory

@Scope("singleton")
public class InternalDatabase {

  private List<Transaction> memory;

  public InternalDatabase() {
    if (this.memory == null) {
      this.memory = new ArrayList<>();
    }
  }

  public Mono<Transaction> add (Transaction t) {
    this.memory.add(t);
    return Mono.just(t);
  }

  public Flux<Transaction> get() {
    return Flux
      .fromIterable(new ArrayList<>(this.memory))
      .delayElements(Duration.ofMillis(300));
  }

}

Pour simuler de la latence lors de la récupération de nos objets en mémoire, nous ajoutons un délai avec delayElements().

Pour exposer le tout, notre contrôleur se charge de fournir une interface REST.

Contrôler des transactions

@RestController
@RequestMapping("/transactions")
public class TransactionController {

  @Autowired
  private InternalDatabase internalDatabase;

  @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
  public Flux<Transaction> findAll() {
    return this.internalDatabase.get();
  }

  @PostMapping
  public Mono<ResponseEntity<Transaction>> save(@RequestParam float amount) {
    Transaction t = new Transaction(amount);
    return this.internalDatabase.add(t)
            .map(savedTransaction -> new ResponseEntity<Transaction>(savedTransaction, HttpStatus.CREATED));
  }

}

Flux<> est utilisé pour la récupération de plusieurs objets, contrairement à Mono<> qui n’est utilisé que pour un seul objet ou aucun.

En produisant du texte en streaming avec MediaType.TEXT_EVENT_STREAM_VALUE , nous avons maintenant la possibilité de récupérer au fur et à mesure nos données en mémoire.

En conclusion, privilégiez une architecture réactive pour assurer le succès de vos applications 👍


Retrouvez l’ensemble des sources utilisées sur mon Github: https://github.com/lnalex/springwebflux-reactive-example

comments powered by Disqus