Share
Architecture découplée grâce aux Reactive Extensions
Auparavant, les applications se connectaient à une seule base de données. Aujourd’hui, elles doivent communiquer avec un ensemble de base de données et composer avec des services web distants. Elles ne sont plus des blocs monolithiques, mais distribuées sur différents serveurs sur différentes zones géographiques. Elles sont “scalables” mais elles doivent maintenant attendre les réponses d’autres applications.
Comment profiter de ces temps d’attente ? Comment casser ce couplage temporel ? Comment transformer un couplage applicatif fort en couplage lâche ?
Cette université fera le tour de RxJava et RxJs. Comment les mettre en place sur un projet et dans une équipe. Et surtout comment utiliser ces bibliothèques pour découpler votre architecture.
University - 20 Avril 2016 - Devoxx France (Palais des congrès de Paris)
David Wursteisen - Brice Dutheil
Note cette présentation est la même que https://speakerdeck.com/dwursteisen/architecture-decouplee-grace-aux-reactive-extensions
Vidéo : https://youtu.be/4To7s3qln2s
Brice Dutheil
Transcript
496 slides
-
#DevoxxFR #Rx Architecture Découplée grâce aux Reactive Extensions @BriceDutheil @dwursteisen
Mercredi 20 Avril - Amphi bleu - 9h30 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ce que nous allons voir Reactive
Extensions en théorie -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ce que nous allons voir Reactive
Extensions en théorie en pratique -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ce que nous allons voir Reactive
Extensions en théorie en pratique sur notre application -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ce que nous allons voir Reactive
Extensions en théorie en pratique sur notre application Pause -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ce que nous ne verrons pas
Énumération des opérateurs -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ce que nous ne verrons pas
Énumération des opérateurs Mieux que ces alternatives ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ce que nous ne verrons pas
Énumération des opérateurs Mieux que ces alternatives ? Expliquer tous les patterns d’utilisation possible -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / BIO client OS Négociation
TCP Requête # nouvelle requête connexion établie prête à être consommée -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / BIO client OS Négociation
TCP Requête # nouvelle requête Acceptor Thread Accepte la requête connexion établie prête à être consommée acceptedCount = -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / BIO client OS Négociation
TCP Requête # nouvelle requête Acceptor Thread connexion établie prête à être consommée Accepte la requête Requête donnée aux threads de travail si slot disponible Worker Executor maxThreads = -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / BIO client OS Négociation
TCP Requête # nouvelle requête Acceptor Thread connexion établie prête à être consommée Accepte la requête Requête donnée aux threads de travail si slot disponible Thread -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / BIO client OS Négociation
TCP Requête # nouvelle requête Acceptor Thread connexion établie prête à être consommée Accepte la requête Requête donnée aux threads de travail si slot disponible Thread w a r Lit la payload war -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / BIO client OS Négociation
TCP Requête # nouvelle requête Acceptor Thread connexion établie prête à être consommée Accepte la requête Requête donnée aux threads de travail si slot disponible Thread w a r Lit la payload Business war -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / BIO client OS Négociation
TCP Requête # nouvelle requête Acceptor Thread connexion établie prête à être consommée Accepte la requête Requête donnée aux threads de travail si slot disponible Thread w a r Lit la payload Écrit la réponse, ferme la connexion Business war -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / BIO client OS Négociation
TCP Requête # nouvelle requête Acceptor Thread connexion établie prête à être consommée Accepte la requête Requête donnée aux threads de travail si slot disponible Thread war Lit la payload Écrit la réponse, ferme la connexion Business -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat client OS Négociation TCP Requête
# nouvelle requête Acceptor Thread connexion établie prête à être consommée Accepte la requête Requête donnée aux threads de travail si slot disponible Thread w a r Lit la payload Écrit la réponse, ferme la connexion Business war -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Client Nouvelle requête Thread war Plus
de requêtes en base Plus long à répondre -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Client Nouvelle requête Thread webservice.api microservice
# war Avec d’autres dépendances -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat client OS Requête # nouvelle
requête Acceptor Thread Attend un slot disponible Thread war Executor full -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Point de vue du CPU Pas
d’instruction à exécuter Le système attend sur des I/Os -
@BriceDutheil @dwursteisen #DevoxxFR #Rx On attend Services distants Base de
données Fournisseurs tiers -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Localement aussi Lecture/Écriture sur disque -
ex : swap Panne hardware - ex : disque en panne augmente la latence -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Sur Tomcat, utiliser un autre connecteur
? NIO, APR, NIO2 (Tomcat 8) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx NIO ne résout pas le problème
de fond §9.2 The Container Provider should ensure that the dispatch of the request to a target servlet occurs in the same thread of the same JVM as the original request. http://download.oracle.com/otn-pub/jcp/servlet-3.0-mrel-full-oth-JSpec/servlet-3_0-mrel-spec.pdf -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / NIO client OS Requête
# nouvelle requête Acceptor Thread Thread Poller Thread Requête # war -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Tomcat / NIO client OS Requête
# nouvelle requête Acceptor Thread Thread Poller Thread Requête # war -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Améliorer le code de notre application
• Executors • Future -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Améliorer le code de notre application
• Executors • Future • Embarquer une techno à la ActiveMQ ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Améliorer le code de notre application
• Executors • Future • Embarquer une techno à la ActiveMQ ? • Problème de lourdeur, overhead -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Améliorer le code de notre application
• Executors • Future • Embarquer une techno à la ActiveMQ ? • Problème de lourdeur, overhead • Quid de la composition -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Améliorer le code de notre application
• Executors • Future • Embarquer une techno à la ActiveMQ ? • Problème de lourdeur, overhead • Quid de la composition • CompletableFuture => Avec Collections ou Streams ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Améliorer le code de notre application
• Executors • Future • Embarquer une techno à la ActiveMQ ? • Problème de lourdeur, overhead • Quid de la composition • CompletableFuture => Avec Collections ou Streams ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Améliorer le code de notre application
• Executors • Future • Embarquer une techno à la ActiveMQ ? • Problème de lourdeur, overhead • Quid de la composition • CompletableFuture => Avec Collections ou Streams ? • Gestion d’erreurs -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Le code pour mitiger ces problèmes
est plus obscur au raisonnement -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Quelles solutions ? Tuning Tomcat Pool
JDBC … Changement du code -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Pourquoi faire ? Une API pour
la programmation asynchrone -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Appel Synchrone - Asynchrone Appel synchrone
versus Appel asynchrone -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Temporalité http://blog.codinghorror.com/the-infinite-space-between-words/ Temps machine Temps humain
1 cycle CPU 0.3 nanosecondes 1 seconde Accès disque (SSD) 50-150 microsecondes 2-6 jours Accès disque (mécanique) 1-10 millisecondes 1-12 mois Envoi d’un paquet de San Francisco à New York 40 milliseconds 4 ans -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Temporalité http://blog.codinghorror.com/the-infinite-space-between-words/ Temps machine Temps humain
1 cycle CPU 0.3 nanosecondes 1 seconde Accès disque (SSD) 50-150 microsecondes 2-6 jours Accès disque (mécanique) 1-10 millisecondes 1-12 mois Envoi d’un paquet de San Francisco à New York 40 milliseconds 4 ans -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Pour quoi faire ? Future<String> future
= … future.get(); // bloquant ! -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Pour quoi faire ? future1.get(); future2.get();
future3.get(); future4.get(); future5.get(); Ordonnancement optimal ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Pour quoi faire ? client.execute(new Callback()
{ @Override public void completed(HttpResponse response) { } }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Pour quoi faire ? client.execute(new Callback()
{ @Override public void completed(HttpResponse response) { client.execute(new Callback() { @Override public void completed(HttpResponse response) { } }); } }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Pour quoi faire ? client.execute(new Callback()
{ @Override public void completed(HttpResponse response) { client.execute(new Callback() { @Override public void completed(HttpResponse response) { client.execute(new Callback() { @Override public void completed(HttpResponse response) { } }); } }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Pour quoi faire ? client.execute(new Callback()
{ @Override public void completed(HttpResponse response) { client.execute(new Callback() { @Override public void completed(HttpResponse response) { client.execute(new Callback() { @Override public void completed(HttpResponse response) { } }); } }); Callback hell -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxJava permet de manipuler des évènements
d’une manière synchrone et/ou asynchrone -
@BriceDutheil @dwursteisen #DevoxxFR #Rx λ ϕ φ json json json
Observable<json> Observable<Integer> -
@BriceDutheil @dwursteisen #DevoxxFR #Rx λ ϕ φ json json json
Observable<json> Observable<Integer> Observable<Click> -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .subscribe(new Observer<Integer>() {
@Override public void onNext(Integer v) { System.out.println("Valeur : " + v); } @Override public void onCompleted() { System.out.println("Fin"); } @Override public void onError(Throwable e) { System.out.println("Erreur"); } }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .subscribe(new Observer<Integer>() {
@Override public void onNext(Integer v) { System.out.println("Valeur : " + v); } @Override public void onCompleted() { System.out.println("Fin"); } @Override public void onError(Throwable e) { System.out.println("Erreur"); } }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .subscribe(new Observer<Integer>() {
@Override public void onNext(Integer v) { System.out.println("Valeur : " + v); } @Override public void onCompleted() { System.out.println("Fin"); } @Override public void onError(Throwable e) { System.out.println("Erreur"); } }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .subscribe(new Observer<Integer>() {
@Override public void onNext(Integer v) { System.out.println("Valeur : " + v); } @Override public void onCompleted() { System.out.println("Fin"); } @Override public void onError(Throwable e) { System.out.println("Erreur"); } }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .subscribe(v-> System.out.println("Valeur :
" + v), e -> System.out.println("Erreur"), () -> System.out.println("Fin") ); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .subscribe(v-> System.out.println("Valeur :
" + v), e -> System.out.println("Erreur"), () -> System.out.println("Fin") ); onNext onError onCompleted -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .map(i -> i
* 2) .take(2) .defaultIfEmpty(0) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .map(i -> i
* 2) .take(2) .defaultIfEmpty(0) .subscribe(); Opérateur map -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .map(i -> i
* 2) .take(2) .defaultIfEmpty(0) .subscribe(); Opérateur take -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .map(i -> i
* 2) .take(2) .defaultIfEmpty(0) .subscribe(); Opérateur defaultIfEmpty -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Question : Comment convertir une liste
de mots en minuscules et filtrer pour garder uniquement les mots de plus de 3 lettres ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just("bjr", "DevoXx") .map(str -> str.toLowerCase()) .filter(str
-> str.length() > 3) .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just("bjr", "DevoXx") .map(str -> str.toLowerCase()) .filter(str
-> str.length() > 3) .subscribe(System.out::println); Conversion -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just("bjr", "DevoXx") .map(str -> str.toLowerCase()) .filter(str
-> str.length() > 3) .subscribe(System.out::println); Filtre -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just("bjr", "DevoXx") .map(str -> str.toLowerCase()) .filter(str
-> str.length() > 3) .subscribe(System.out::println); Observable 1 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just("bjr", "DevoXx") .map(str -> str.toLowerCase()) .filter(str
-> str.length() > 3) .subscribe(System.out::println); Observable 2 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just("bjr", "DevoXx") .map(str -> str.toLowerCase()) .filter(str
-> str.length() > 3) .subscribe(System.out::println); Observable 3 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Concept de Monade Une structure ayant
- Un type paramétré (Observable<T>) - Une factory (Observable.just(...)) - Une méthode flatMap (obs.flatMap(...)) - Structure programmatique (Grossièrement) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Structure programmatique List<String> words = Stream.of("bonJour",
"bonjour", "Hello") .map(str -> str.toLowerCase()) .distinct() .collect(Collectors.toList()); Factory -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Structure programmatique List<String> words = Stream.of("bonJour",
"bonjour", "Hello") .map(str -> str.toLowerCase()) .distinct() .collect(Collectors.toList()); Étape 1 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Structure programmatique List<String> words = Stream.of("bonJour",
"bonjour", "Hello") .map(str -> str.toLowerCase()) .distinct() .collect(Collectors.toList()); Étape 2 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Structure programmatique List<String> words = Stream.of("bonJour",
"bonjour", "Hello") .map(str -> str.toLowerCase()) .distinct() .collect(Collectors.toList()); Étape 3 (fonction terminale) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Structure programmatique Observable.just("bonJour", "bonjour", "Hello") .map(str
-> str.toLowerCase()) .distinct() .subscribe(); Factory -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Structure programmatique Observable.just("bonJour", "bonjour", "Hello") .map(str
-> str.toLowerCase()) .distinct() .subscribe(); Étape 1 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Structure programmatique Observable.just("bonJour", "bonjour", "Hello") .map(str
-> str.toLowerCase()) .distinct() .subscribe(); Étape 2 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Structure programmatique Observable.just("bonJour", "bonjour", "Hello") .map(str
-> str.toLowerCase()) .distinct() .subscribe(); Étape 3 (fonction terminale) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Avec les Reactive Extensions, nous allons
principalement manipuler des Observables -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Évitez les effets de bord Structure
programmatique une fonction est dite à effet de bord si elle modifie un état autre que sa valeur de retour -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Évitez les effets de bord Structure
programmatique une fonction est dite à effet de bord si elle modifie un état autre que sa valeur de retour (Comme avec les streams Java 8) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Question : Comment créer une liste
à partir d’un ensemble de valeurs ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Effets de bord List<Integer> result =
new ArrayList<>(); Observable.just(1, 2, 3) .map(i -> result.add(i)) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx List<Integer> result = new ArrayList<>(); Observable.just(1,
2, 3) .map(i -> result.add(i)) .subscribe(); Effets de bord Effet de bord -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .toList() .subscribe(result ->
{/* ... */}); Effets de bord Pas d’effet de bord -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .toList() .subscribe(result ->
{/* ... */}); Effets de bord Consommation de la liste -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Effets de bord Observable.just(1, 2, 3)
.map(i -> { LOGGER.info("value : " + i); return i; }) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .map(i -> {
LOGGER.info("value : " + i); return i; }) .subscribe(); Effets de bord Effet de bord -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Effets de bord Observable.just(1, 2, 3)
// explicite .doOnNext(i -> LOGGER.info("Valeur :" + i)) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Effets de bord Observable.just(1, 2, 3)
.doOnSubscribe(() -> LOGGER.info("Subscribe")) .doOnNext(i -> LOGGER.info("Valeur :" + i)) .doOnTerminate(() -> LOGGER.info("Terminate")) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Effets de bord Observable.just(1, 2, 3)
.doOnSubscribe(() -> LOGGER.info("Subscribe")) .doOnNext(i -> LOGGER.info("Valeur :" + i)) .doOnTerminate(() -> LOGGER.info("Terminate")) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Effets de bord Observable.just(1, 2, 3)
.doOnSubscribe(() -> LOGGER.info("Subscribe")) .doOnNext(i -> LOGGER.info("Valeur :" + i)) .doOnTerminate(() -> LOGGER.info("Terminate")) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Effets de bord Observable.just(1, 2, 3)
.doOnSubscribe(() -> LOGGER.info("Subscribe")) .doOnNext(i -> LOGGER.info("Valeur :" + i)) .doOnTerminate(() -> LOGGER.info("Terminate")) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Question : Comment compter le nombre
d’erreur de différents Observables ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx // attention ! état partagé !
AtomicLong errors = new AtomicLong(0); Observable.just(1, 2, 3) .doOnError(ex -> errors.incrementAndGet()) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx // attention ! état partagé !
AtomicLong errors = new AtomicLong(0); Observable.just(1, 2, 3) .doOnError(ex -> errors.incrementAndGet()) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Comment créer des Observables Observable.just() Observable.fromCallable()
Observable.defer() Observable.using() Observable.create() Complexité -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Comment créer des Observables Observable.just() Observable.fromCallable()
Observable.defer() Observable.using() Observable.create() Simple d’utilisation -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Comment créer des Observables Observable.just() Observable.fromCallable()
Observable.defer() Observable.using() Observable.create() Complexe d’utilisation -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Comment créer des Observables Observable.just() Observable.fromCallable()
Observable.defer() Observable.using() Observable.create() Complexe d’utilisation Offre un maximum de contrôle -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.using( () -> new DB(), db
-> Observable.just(db.doQuery()), db -> db.closeDb()) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.using( () -> new DB(), db
-> Observable.just(db.doQuery()), db -> db.closeDb()) .subscribe(); Création d’une ressource -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.using( () -> new DB(), db
-> Observable.just(db.doQuery()), db -> db.closeDb()) .subscribe(); Émission d’un résultat -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.using( () -> new DB(), db
-> Observable.just(db.doQuery()), db -> db.closeDb()) .subscribe(); Fermeture de la ressource -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { subscriber.add(/* ... */);
subscriber.setProducer(n -> { subscriber.onNext(/* ... */); // ... subscriber.onCompleted(); }); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { subscriber.add(/* ... */);
subscriber.setProducer(n -> { subscriber.onNext(/* ... */); // ... subscriber.onCompleted(); }); }).subscribe(); ? ? ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { subscriber.add(/* ... */);
subscriber.setProducer(n -> { subscriber.onNext(/* ... */); // ... subscriber.onCompleted(); }); }).subscribe(); Désabonnement Backpressure Contrat Rx -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { for (int i
= 0; i < 10; i++) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(compute(i)); } } subscriber.onCompleted(); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { for (int i
= 0; i < 10; i++) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(compute(i)); } } subscriber.onCompleted(); }).subscribe(); Le flux est-il encore actif ? Éviter de lancer ce calcul (coûteux) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { DB db =
new DB(); subscriber.add(Subscriptions.create(() -> db.closeDb())); subscriber.onNext(db); subscriber.onCompleted(); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { DB db =
new DB(); subscriber.add(Subscriptions.create(() -> db.closeDb())); subscriber.onNext(db); subscriber.onCompleted(); }).subscribe(); Code de désabonnement -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.using( () -> new DB(), db
-> Observable.just(db.doQuery()), db -> db.closeDb()) .subscribe(); Plus simple ! -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create permet de gérer le contrat
Rx et le désabonnement et la backpressure -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create vous oblige à gérer le
contrat Rx et le désabonnement et la backpressure -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Connaissez vous d’autres modèles de concurrence
? Non Thread uniquement Oui Acteur, STM, Channels … -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Connaissez vous vous d’autres modèles de
concurrence ? Non Thread uniquement -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Connaissez vous vous d’autres modèles de
concurrence ? Oui Acteur, STM, Channels … -
@BriceDutheil @dwursteisen #DevoxxFR #Rx On a vu Tomcat et les
servlets OS Tomcat / BIO client Requête # Acceptor Thread Thread war -
@BriceDutheil @dwursteisen #DevoxxFR #Rx method() { Observable.from(items) .map(this::transform) .flatMap(this::computeRelativeItems) .subscribe(response::resume,
response::resume); } Travail sur le Thread courant -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Par défaut synchrone* method() { Observable.from(items)
.map(this::transform) .flatMap(this::computeRelativeItems) .subscribe(response::resume, response::resume); } Travail sur le Thread courant -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxJava permet un contrôle explicite de
la concurrence avec observeOn() ou avec subscribeOn() -
@BriceDutheil @dwursteisen #DevoxxFR #Rx method() { Observable.from(items) .observeOn( ) .map(this::transform)
.flatMap(this::computeRelativeItems) .subscribe(response::resume, response::resume); } Thread courant -
@BriceDutheil @dwursteisen #DevoxxFR #Rx method() { Observable.from(items) .observeOn( ) .map(this::transform)
.flatMap(this::computeRelativeItems) .subscribe(response::resume, response::resume); } Thread courant -
@BriceDutheil @dwursteisen #DevoxxFR #Rx method() { Observable.from(items) .observeOn(Schedulers.computation()) .map(this::transform) .flatMap(this::computeRelativeItems)
.subscribe(response::resume, response::resume); } Thread courant Travail planifié sur computation -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Par défaut synchrone* method() { Observable.from(items)
.map(this::transform) .delay(10, SECONDS)) .subscribe(response::resume, response::resume); } Travail sur le Thread courant * Certains opérateurs fonctionnent par défaut sur un autre Scheduler -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Par défaut synchrone* method() { Observable.from(items)
.map(this::transform) .delay(10, SECONDS)) .subscribe(response::resume, response::resume); } Travail sur le Thread courant * Certains opérateurs fonctionnent par défaut sur un autre Scheduler Change le contexte -
@BriceDutheil @dwursteisen #DevoxxFR #Rx method() { Observable.from(items) .observeOn(Schedulers.computation()) .map(this::transform) .flatMap(this::computeRelativeItems)
.subscribe(response::resume, response::resume); } Thread courant Travail planifié sur computation -
@BriceDutheil @dwursteisen #DevoxxFR #Rx method() { Observable.from(items) .observeOn(Schedulers.computation()) .map(this::transform) .flatMap(this::computeRelativeItems)
.subscribeOn( ) .subscribe(response::resume, response::resume); } Thread courant Travail planifié sur computation -
@BriceDutheil @dwursteisen #DevoxxFR #Rx method() { Observable.from(items) .observeOn(Schedulers.computation()) .map(this::transform) .flatMap(this::computeRelativeItems)
.subscribeOn( ) .subscribe(response::resume, response::resume); } Thread courant Travail planifié sur computation -
@BriceDutheil @dwursteisen #DevoxxFR #Rx method() { Observable.from(items) .observeOn(Schedulers.computation()) .map(this::transform) .flatMap(this::computeRelativeItems)
.subscribeOn(Schedulers.io()) .subscribe(response::resume, response::resume); } Thread courant Travail planifié sur computation Production des éléments sur io -
@BriceDutheil @dwursteisen #DevoxxFR #Rx subscribeOn() définit le contexte où s’exécute
la production des éléments -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Il ne peut y avoir qu’un
seul subscribeOn() Premier arrivé, premier servi ! -
@BriceDutheil @dwursteisen #DevoxxFR #Rx computation() .map(str -> …) Élément #
.filter(…) Élément <> .merge(…) Élément ‘a’ .map(str -> …) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx computation() Nombre de coeur CPU .map(str
-> …) Élément # .filter(…) Élément <> .merge(…) Élément ‘a’ .map(str -> …) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx computation() Nombre de coeur CPU .debounce(…)
.filter(e -> ...) .merge(...) Élément # .filter(…) Élément <> .merge(…) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Le modèle de computation() est une
boucle d’évènements -
@BriceDutheil @dwursteisen #DevoxxFR #Rx io() Élément # .filter(…) Élément <>
.merge(…) Élément ‘a’ .flatMap(…) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx io() DB Élément # .filter(…) Élément
<> .merge(…) Élément ‘a’ .flatMap(…) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx io() DB Élément # .filter(…) Élément
<> .merge(…) Élément ‘a’ .flatMap(…) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx io() DB Network Internet Élément #
.filter(…) Élément <> .merge(…) Élément ‘a’ .flatMap(…) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx io() DB Network Internet Élément #
.filter(…) Élément <> .merge(…) Élément ‘a’ .flatMap(…) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx io() DB Network Internet Élément #
.filter(…) Élément <> .merge(…) Élément ‘a’ .flatMap(…) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Le modèle du scheduler io() est
un pool de thread qui grossit au besoin -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public Response wait(@PathParam("heavyness") int heavyness) throws InterruptedException { System.out.format("heavy work that will take %dms%n", heavyness); MILLISECONDS.sleep(heavyness); // hard and long work return Response.ok(format("Waited %dms", heavyness)).build(); } } -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public Response wait(@PathParam("heavyness") int heavyness) throws InterruptedException { System.out.format("heavy work that will take %dms%n", heavyness); MILLISECONDS.sleep(heavyness); // hard and long work return Response.ok(format("Waited %dms", heavyness)).build(); } } Travail -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { executor.submit(() -> { try { MILLISECONDS.sleep(heavyness); ar.resume(format("Waited %dms", heavyness)); } catch (InterruptedException e) { Thread.interrupted(); ar.resume(e); } }); } } -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { executor.submit(() -> { try { MILLISECONDS.sleep(heavyness); ar.resume(format("Waited %dms", heavyness)); } catch (InterruptedException e) { Thread.interrupted(); ar.resume(e); } }); } } JAX-RS 2.0 (Servlet 3.0) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { executor.submit(() -> { try { MILLISECONDS.sleep(heavyness); ar.resume(format("Waited %dms", heavyness)); } catch (InterruptedException e) { Thread.interrupted(); ar.resume(e); } }); } } JAX-RS 2.0 (Servlet 3.0) Tache asynchrone -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { executor.submit(() -> { try { MILLISECONDS.sleep(heavyness); ar.resume(format("Waited %dms", heavyness)); } catch (InterruptedException e) { Thread.interrupted(); ar.resume(e); } }); } } JAX-RS 2.0 (Servlet 3.0) thread pool Tache asynchrone -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { executor.submit(() -> { try { MILLISECONDS.sleep(heavyness); ar.resume(format("Waited %dms", heavyness)); } catch (InterruptedException e) { Thread.interrupted(); ar.resume(e); } }); } } JAX-RS 2.0 (Servlet 3.0) thread pool Tache asynchrone response.resume() pour retourner la réponse HTTP -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { response.setTimeout(50, MILLISECONDS); Observable.just(waitTime) .observeOn(Schedulers.computation()) .map(this::longProcess) .subscribe(result -> ar.resume(result), throwable -> ar.resume(throwable)); } private String longProcess(int heavyness) { ... } } -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { response.setTimeout(50, MILLISECONDS); Observable.just(waitTime) .observeOn(Schedulers.computation()) .map(this::longProcess) .subscribe(result -> ar.resume(result), throwable -> ar.resume(throwable)); } private String longProcess(int heavyness) { ... } } Traitement asynchrone avec Rx -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { response.setTimeout(50, MILLISECONDS); Observable.just(waitTime) .observeOn(Schedulers.computation()) .map(this::longProcess) .subscribe(result -> ar.resume(result), throwable -> ar.resume(throwable)); } private String longProcess(int heavyness) { ... } } Prend les paramètres -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { response.setTimeout(50, MILLISECONDS); Observable.just(waitTime) .observeOn(Schedulers.computation()) .map(this::longProcess) .subscribe(result -> ar.resume(result), throwable -> ar.resume(throwable)); } private String longProcess(int heavyness) { ... } } Scheduler sur lequel faire le traitement ≈≈> executor -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { response.setTimeout(50, MILLISECONDS); Observable.just(waitTime) .observeOn(Schedulers.computation()) .map(this::longProcess) .subscribe(result -> ar.resume(result), throwable -> ar.resume(throwable)); } private String longProcess(int heavyness) { ... } } Sera exécuté sur un Computation thread Thread de la servlet -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { response.setTimeout(50, MILLISECONDS); Observable.just(waitTime) .observeOn(Schedulers.computation()) .map(this::longProcess) .subscribe(result -> ar.resume(result), throwable -> ar.resume(throwable)); } private String longProcess(int heavyness) { ... } } Travail -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @Path("heavy-stuff") public static class WaitResource {
@GET @Path("{heavyness}") public void wait(@Suspended AsyncResponse ar, @PathParam("heavyness") int heavyness) { response.setTimeout(50, MILLISECONDS); Observable.just(waitTime) .observeOn(Schedulers.computation()) .map(this::longProcess) .subscribe(result -> ar.resume(result), throwable -> ar.resume(throwable)); } private String longProcess(int heavyness) { ... } } onNext : résultat du traitement onError : termine la requête HTTP avec une erreur -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Traitement toujours long = Attente pour
le client = Toujours besoin de threads -
@BriceDutheil @dwursteisen #DevoxxFR #Rx asyncResponse.setTimeout(50, MILLISECONDS); Retourne 503 Service Unavailable
MAIS le travail en cours est toujours actif -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Introduire un TimeoutHandler Status HTTP Headers
Entité personnalisée Arrêt / interruption des traitements ... -
@BriceDutheil @dwursteisen #DevoxxFR #Rx … mais limite l’intégration avec RxJava,
par les contraintes de la spec. -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Netflix, Spotify, Twitter, Uber, d’autres sont
allés dans cette direction. -
@BriceDutheil @dwursteisen #DevoxxFR #Rx L’architecture microservices est une opportunité pour
explorer les alternatives -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxNetty.createHttpServer(port, (request, response) -> { if
(request.getHttpMethod() == HttpMethod.POST) { return request.getContent() .map(TehServer::parseBody) .map(TehServer::longStuff) .flatMap(result -> { response.setStatus(HttpResponseStatus.OK); response.writeString(result); return response.close(); }).ignoreElements(); } return response.close(false); }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxNetty.createHttpServer(port, (request, response) -> { if
(request.getHttpMethod() == HttpMethod.POST) { return request.getContent() .map(TehServer::parseBody) .map(TehServer::longStuff) .flatMap(result -> { response.setStatus(HttpResponseStatus.OK); response.writeString(result); return response.close(); }).ignoreElements(); } return response.close(false); }); Créer un serveur -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxNetty.createHttpServer(port, (request, response) -> { if
(request.getHttpMethod() == HttpMethod.POST) { return request.getContent() .map(TehServer::parseBody) .map(TehServer::longStuff) .flatMap(result -> { response.setStatus(HttpResponseStatus.OK); response.writeString(result); return response.close(); }).ignoreElements(); } return response.close(false); }); Handler pour gérer la requête -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxNetty.createHttpServer(port, (request, response) -> { if
(request.getHttpMethod() == HttpMethod.POST) { return request.getContent() .map(TehServer::parseBody) .map(TehServer::longStuff) .flatMap(result -> { response.setStatus(HttpResponseStatus.OK); response.writeString(result); return response.close(); }).ignoreElements(); } return response.close(false); }); Retourne un Observable -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxNetty.createHttpServer(port, (request, response) -> { if
(request.getHttpMethod() == HttpMethod.POST) { return request.getContent() .map(TehServer::parseBody) .map(TehServer::longStuff) .flatMap(result -> { response.setStatus(HttpResponseStatus.OK); response.writeString(result); return response.close(); }).ignoreElements(); } return response.close(false); }); Travail sur la request avec RxJava -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxNetty s’utilise pour des services très
simples websocket, http, tcp, … -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxNetty offre une API de bas
niveau pour plus de contrôle -
@BriceDutheil @dwursteisen #DevoxxFR #Rx … mais demande un effort appréciable
sur la mise en oeuvre des aspects d’une API HTTP publique -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { } Exemple : un stub JAX-RS -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .subscribe(response::resume); } Un Observable qui renvoie quelque chose sur AsyncResponse -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { … }) .subscribe(response::resume); } Ici seront fait les appels -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Response aResponse = wsA(lastNumber); … }) .subscribe(response::resume); } Appel au service A -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<Response> aObs = Observable.fromCallable(() -> wsA(lastNumber)); … }) .subscribe(response::resume); } Appel observable au service A -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsA(lastNumber)) .map(r -> r.readEntity(String.class)); … }) .subscribe(response::resume); } Transformation de la payload -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsA(lastNumber)) .map(r -> r.readEntity(String.class)); Observable<String> bObs = Observable.fromCallable(() -> wsB(lastNumber)) .map(r -> r.readEntity(String.class)); … }) .subscribe(response::resume); } Appel observable au service B -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsA(lastNumber)) .map(r -> r.readEntity(String.class)); Observable<String> bObs = Observable.fromCallable(() -> wsB(lastNumber)) .map(r -> r.readEntity(String.class)); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume); } Composition de A et de B -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsA(lastNumber)) .map(r -> r.readEntity(String.class)); Observable<String> bObs = Observable.fromCallable(() -> wsB(lastNumber)) .map(r -> r.readEntity(String.class)); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, response::resume); } Ajout d’un handler onError -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsA(lastNumber)) .subscribeOn(Schedulers.io()) .map(r -> r.readEntity(String.class)); Observable<String> bObs = Observable.fromCallable(() -> wsB(lastNumber)) .subscribeOn(Schedulers.io()) .map(r -> r.readEntity(String.class)); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, response::resume); } Appels réseau exécutés sur des threads dédiés aux I/O -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsA(lastNumber)) .subscribeOn(Schedulers.io()) .flatMap(r -> { if (r.getStatus() == 503) { return Observable.error( new ServiceUnavailable("service A")); } return just(r.readEntity(String.class)); }); Observable<String> bObs = Observable.fromCallable(() -> wsB(lastNumber)) .subscribeOn(Schedulers.io()) .map(r -> r.readEntity(String.class)); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, response::resume); } Gestion du statut HTTP 503 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsServiceA(lastNumber)) .subscribeOn(Schedulers.io()) .flatMap(r -> { if (r.getStatus() != 200) { return Observable.error( new ServiceUnavailable("service A")); } return Observable.just(r.readEntity(String.class)); }); Observable<String> bObs = Observable.fromCallable(() -> wsServiceB(lastNumber)) .subscribeOn(Schedulers.io()) .map(r -> r.readEntity(String.class)); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, (error) -> { response.resume(error); }); } AsyncReponse.resume(Throwable) ⇒ 500 Server Error -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsServiceA(lastNumber)) .subscribeOn(Schedulers.io()) .flatMap(r -> { if (r.getStatus() != 200) { return Observable.error(new ServiceUnavailable("service A")); } return Observable.just(r.readEntity(String.class)); }); Observable<String> bObs = Observable.fromCallable(() -> wsServiceB(lastNumber)) .subscribeOn(Schedulers.io()) .map(r -> r.readEntity(String.class)); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, (error) -> { response.resume(Response.serverError() .entity(error.getMessage()) .build()); }); } Personnalisation de la payload d’erreur -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsServiceA(lastNumber)) .subscribeOn(Schedulers.io()) .flatMap(r -> { if (r.getStatus() != 200) { return Observable.error(new ServiceUnavailable("service A")); } return Observable.just(r.readEntity(String.class)); }); Observable<String> bObs = Observable.fromCallable(() -> wsServiceB(lastNumber)) .subscribeOn(Schedulers.io()) .map(r -> r.readEntity(String.class)); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, (error) -> { response.resume(Response.status(((HttpServiceEx) error).httpStatus) .entity(error.getMessage()) .build()); }); } Propagation du statut HTTP -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeInterestingValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsServiceA(lastNumber)) .subscribeOn(Schedulers.io()) .flatMap(r -> { if (r.getStatus() != 200) { return Observable.error(new ServiceUnavailable("service A")); } return Observable.just(r.readEntity(String.class)); }) .retry(2); Observable<String> bObs = Observable.fromCallable(() -> wsServiceB(lastNumber)) .subscribeOn(Schedulers.io()) .map(r -> r.readEntity(String.class)); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, (error) -> { response.resume(Response.status(((HttpServiceException) error).proposedHttpStatus) .entity(error.getMessage()) .build()); }); } Recommencera 2 fois -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> makeValue()) .flatMap(lastNumber -> { Observable<String> aObs = Observable.fromCallable(() -> wsServiceA(lastNumber)) .subscribeOn(Schedulers.io()) .flatMap(r -> { if (r.getStatus() != 200) { return Observable.error(new ServiceUnavailable("service A")); } return Observable.just(r.readEntity(String.class)); }) .retry(2); Observable<String> bObs = Observable.fromCallable(() -> wsServiceB(lastNumber)) .subscribeOn(Schedulers.io()) .map(r -> r.readEntity(String.class)) .timeout(500, MILLISECONDS, error(new ServiceTimeout("service B"))); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, (error) -> { response.resume(Response.status(((HttpServiceException) error).proposedHttpStatus) .entity(error.getMessage()) .build()); }); } Défini un délai acceptable -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Hystrix permet de résoudre ces challenges
de façon plus pertinente -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> new SecureRandom().nextInt(1000)) .flatMap(lastNumber -> { Observable<String> aObs = new ServiceACommand(lastNumber).observe(); Observable<String> bObs = new ServiceBCommand(lastNumber).observe(); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, (error) -> { response.resume(Response.statusError() .entity(error.getMessage()) .build()); }); } Commandes Hystrix -
@BriceDutheil @dwursteisen #DevoxxFR #Rx @GET public void wait(@Suspended final AsyncResponse
response) { Observable.fromCallable(() -> new SecureRandom().nextInt(1000)) .flatMap(lastNumber -> { Observable<String> aObs = new ServiceACommand(lastNumber).observe(); Observable<String> bObs = new ServiceBCommand(lastNumber).observe(); return Observable.zip(aObs, bObs, (a, b) -> format("=> Result : %s, %s", a, b); }) .subscribe(response::resume, (error) -> { response.resume(Response.serverError() .entity(error.getMessage()) .build()); }); } Erreur 500 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx class ServiceACommand extends HystrixObservableCommand<String> { private
int number; protected ServiceACommand(int number) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("work group")) .andCommandKey(HystrixCommandKey.Factory.asKey("work")) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(500))); this.number = number; } @Override protected Observable<String> construct() { return Observable.fromCallable(() -> wsServiceB(number)) .map(response -> response.readEntity(String.class)); } @Override protected Observable<String> resumeWithFallback() { return Observable.just("Service A too long"); } } -
@BriceDutheil @dwursteisen #DevoxxFR #Rx class ServiceACommand extends HystrixObservableCommand<String> { private
int number; protected ServiceACommand(int number) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("work group")) .andCommandKey(HystrixCommandKey.Factory.asKey("work")) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(500))); this.number = number; } @Override protected Observable<String> construct() { return Observable.fromCallable(() -> wsServiceB(number)) .map(response -> response.readEntity(String.class)); } @Override protected Observable<String> resumeWithFallback() { return Observable.just("Service A too long"); } } Commande Hystrix Type de retour -
@BriceDutheil @dwursteisen #DevoxxFR #Rx class ServiceACommand extends HystrixObservableCommand<String> { private
int number; protected ServiceACommand(int number) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("work group")) .andCommandKey(HystrixCommandKey.Factory.asKey("work")) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(500))); this.number = number; } @Override protected Observable<String> construct() { return Observable.fromCallable(() -> wsServiceB(number)) .map(response -> response.readEntity(String.class)); } @Override protected Observable<String> resumeWithFallback() { return Observable.just("Service A too long"); } } Commande Hystrix observable Construction -
@BriceDutheil @dwursteisen #DevoxxFR #Rx class ServiceACommand extends HystrixObservableCommand<String> { private
int number; protected ServiceACommand(int number) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("work group")) .andCommandKey(HystrixCommandKey.Factory.asKey("work")) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(500))); this.number = number; } @Override protected Observable<String> construct() { return Observable.fromCallable(() -> wsServiceB(number)) .map(response -> response.readEntity(String.class)); } @Override protected Observable<String> resumeWithFallback() { return Observable.just("Service A too long"); } } Appel observable -
@BriceDutheil @dwursteisen #DevoxxFR #Rx class ServiceACommand extends HystrixObservableCommand<String> { private
int number; protected ServiceACommand(int number) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("work group")) .andCommandKey(HystrixCommandKey.Factory.asKey("work")) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(500))); this.number = number; } @Override protected Observable<String> construct() { return Observable.fromCallable(() -> wsServiceB(number)) .map(response -> response.readEntity(String.class)); } @Override protected Observable<String> resumeWithFallback() { return Observable.just("Service A too long"); } } Plan de secours En cas d’erreur -
@BriceDutheil @dwursteisen #DevoxxFR #Rx class ServiceACommand extends HystrixObservableCommand<String> { private
int number; protected ServiceACommand(int number) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("work group")) .andCommandKey(HystrixCommandKey.Factory.asKey("work")) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(500))); this.number = number; } @Override protected Observable<String> construct() { return Observable.fromCallable(() -> wsServiceB(number)) .map(response -> response.readEntity(String.class)); } @Override protected Observable<String> resumeWithFallback() { return Observable.just("Service A too long"); } } Personnalisation des paramètres de la commande -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ouvert, la dépendance en erreur est
isolée sur une période de temps -
@BriceDutheil @dwursteisen #DevoxxFR #Rx <T> void executeCommand(HystrixObservableCommand<T> command) { command.observe()
.subscribe( result -> Response.ok(result).build(), throwable -> { if (throwable instanceof HystrixRuntimeException) { if (((HystrixRuntimeException) throwable).getFailureType() == FailureType.SHORTCIRCUIT) { Response.status(Status.SERVICE_UNAVAILABLE) .header(HttpHeaders.RETRY_AFTER, command.getProperties() .circuitBreakerSleepWindowInMilliseconds() .get() / 1000); } } Response.serverError().build(); }); } Pour une commande Hystrix -
@BriceDutheil @dwursteisen #DevoxxFR #Rx <T> void executeCommand(HystrixObservableCommand<T> command) { command.observe()
.subscribe( result -> Response.ok(result).build(), throwable -> { if (throwable instanceof HystrixRuntimeException) { if (((HystrixRuntimeException) throwable).getFailureType() == FailureType.SHORTCIRCUIT) { Response.status(Status.SERVICE_UNAVAILABLE) .header(HttpHeaders.RETRY_AFTER, command.getProperties() .circuitBreakerSleepWindowInMilliseconds() .get() / 1000); } } Response.serverError().build(); }); } Si le problème vient de la commande Hystrix -
@BriceDutheil @dwursteisen #DevoxxFR #Rx private <T> void executeCommand(HystrixObservableCommand<T> command) {
command.observe() .subscribe( result -> Response.ok(result).build(), throwable -> { if (throwable instanceof HystrixRuntimeException) { if (((HystrixRuntimeException) throwable).getFailureType() == FailureType.SHORTCIRCUIT) { Response.status(Status.SERVICE_UNAVAILABLE) .header(HttpHeaders.RETRY_AFTER, command.getProperties() .circuitBreakerSleepWindowInMilliseconds() .get() / 1000); } } Response.serverError().build(); }); } Et que le circuit est ouvert -
@BriceDutheil @dwursteisen #DevoxxFR #Rx private <T> void executeCommand(HystrixObservableCommand<T> command) {
command.observe() .subscribe( result -> Response.ok(result).build(), throwable -> { if (throwable instanceof HystrixRuntimeException) { if (((HystrixRuntimeException) throwable).getFailureType() == FailureType.SHORTCIRCUIT) { Response.status(Status.SERVICE_UNAVAILABLE) .header(HttpHeaders.RETRY_AFTER, command.getProperties() .circuitBreakerSleepWindowInMilliseconds() .get() / 1000); } } Response.serverError().build(); }); } Alors proposer au client un prochain créneau -
@BriceDutheil @dwursteisen #DevoxxFR #Rx private <T> void executeCommand(HystrixObservableCommand<T> command) {
command.observe() .subscribe( result -> Response.ok(result).build(), throwable -> { if (throwable instanceof HystrixRuntimeException) { if (((HystrixRuntimeException) throwable).getFailureType() == FailureType.SHORTCIRCUIT) { Response.status(Status.SERVICE_UNAVAILABLE) .header(HttpHeaders.RETRY_AFTER, command.getProperties() .circuitBreakerSleepWindowInMilliseconds() .get() / 1000); } } Response.serverError().build(); }); } -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Hystrix demande une certaine dose de
code Il est possible d’utiliser des annotations avec hystrix-javanica -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Hystrix protège un système distribué de
dépendances en erreur ou hors-délai -
@BriceDutheil @dwursteisen #DevoxxFR #Rx C’est le temps d’une pause Posez
nous des questions directement ou via Twitter -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Les Streams Java 8 peuvent être
plus intéressants. -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Il est possible de faire cohabiter
RxJava et Streams Java8 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3, 4) .toList() .map(list
-> { return list.stream() .filter(i -> i % 2 == 0) .collect(Collectors.toList()); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3, 4) .toList() .map(list
-> { return list.stream() .filter(i -> i % 2 == 0) .collect(Collectors.toList()); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3, 4) .toList() .map(list
-> { return list.stream() .filter(i -> i % 2 == 0) .collect(Collectors.toList()); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx List<Observable<?>> obs = new ArrayList<>(); obs.add(Observable.defer(()
-> Observable.just(1, 2, 3))); obs.add(Observable.just(4, 5, 6)); obs.add(Observable.fromCallable(() -> 7)); Observable.merge(obs).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx List<Observable<?>> obs = new ArrayList<>(); obs.add(Observable.defer(()
-> Observable.just(1, 2, 3))); obs.add(Observable.just(4, 5, 6)); obs.add(Observable.fromCallable(() -> 7)); Observable.merge(obs).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx List<Observable<?>> obs = new ArrayList<>(); obs.add(Observable.defer(()
-> Observable.just(1, 2, 3))); obs.add(Observable.just(4, 5, 6)); obs.add(Observable.fromCallable(() -> 7)); Observable.merge(obs).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx List<Observable<?>> obs = new ArrayList<>(); obs.add(Observable.defer(()
-> Observable.just(1, 2, 3))); obs.add(Observable.just(4, 5, 6)); obs.add(Observable.fromCallable(() -> 7)); Observable.merge(obs).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ce n’est pas parce que l’on
peut le faire que l’on doit le faire. -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable<Feed> feeds = Observable.merge( services.stream() .map(service
-> service.newRequest().user(user).all().observe()) .map(feedObservable -> feedObservable .doOnError(throwable -> log.error(throwable.getMessage(), throwable)) .onErrorResumeNext(Observable.empty())) .collect(Collectors.toList())); feeds.subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable<Feed> feeds = Observable.merge( services.stream() .map(service
-> service.newRequest().user(user).all().observe()) .map(feedObservable -> feedObservable .doOnError(throwable -> log.error(throwable.getMessage(), throwable)) .onErrorResumeNext(Observable.empty())) .collect(Collectors.toList())); feeds.subscribe(); RxJava Stream Java 8 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxView.clickEvents(tapButton) .doOnNext(new Action1<ViewClickEvent>() { @Override public
void call(ViewClickEvent onClickEvent) { LOGGER.debug("--------- GOT A TAP"); } }) .map(new Func1<ViewClickEvent, Integer>() { @Override public Integer call(ViewClickEvent onClickEvent) { return 1; } }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxView.clickEvents(tapButton) .doOnNext(new Action1<ViewClickEvent>() { @Override public
void call(ViewClickEvent onClickEvent) { LOGGER.debug("--------- GOT A TAP"); } }) .map(new Func1<ViewClickEvent, Integer>() { @Override public Integer call(ViewClickEvent onClickEvent) { return 1; } }).subscribe(); Code technique -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxView.clickEvents(tapButton) .doOnNext(event -> LOGGER.debug("--------- GOT A
TAP")) .map(event -> 1) .subscribe(); 3 fois moins de code ! -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Sur Android, vous pouvez utiliser Kotlin
ou Retrolambda ou Java 8 (Jack&Jill) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .flatMap(i -> {
Observable<String> complex = webService.complexify(i) .map(str -> str.toLowerCase()) .retry(4); Observable<String> human = webService.toHumanString(i) .filter(str -> str.length() < 3) .take(1) .onErrorResumeNext(Observable.empty()) .defaultIfEmpty("Oups !"); return Observable.zip(complex, human, (c, h) -> c + h) .flatMap(str -> webService.frequencyOf(str)) .flatMapIterable(itr -> itr); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .flatMap(i -> {
Observable<String> complex = webService.complexify(i) .map(str -> str.toLowerCase()) .retry(4); Observable<String> human = webService.toHumanString(i) .filter(str -> str.length() < 3) .take(1) .onErrorResumeNext(Observable.empty()) .defaultIfEmpty("Oups !"); return Observable.zip(complex, human, (c, h) -> c + h) .flatMap(str -> webService.frequencyOf(str)) .flatMapIterable(itr -> itr); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .flatMap(i -> {
Observable<String> complex = webService.complexify(i) .map(str -> str.toLowerCase()) .retry(4); Observable<String> human = webService.toHumanString(i) .filter(str -> str.length() < 3) .take(1) .onErrorResumeNext(Observable.empty()) .defaultIfEmpty("Oups !"); return Observable.zip(complex, human, (c, h) -> c + h) .flatMap(str -> webService.frequencyOf(str)) .flatMapIterable(itr -> itr); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .flatMap(i -> {
Observable<String> complex = webService.complexify(i) .map(str -> str.toLowerCase()) .retry(4); Observable<String> human = webService.toHumanString(i) .filter(str -> str.length() < 3) .take(1) .onErrorResumeNext(Observable.empty()) .defaultIfEmpty("Oups !"); return Observable.zip(complex, human, (c, h) -> c + h) .flatMap(str -> webService.frequencyOf(str)) .flatMapIterable(itr -> itr); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .flatMap(i -> {
Observable<String> complex = webService.complexify(i) .map(str -> str.toLowerCase()) .retry(4); Observable<String> human = webService.toHumanString(i) .filter(str -> str.length() < 3) .take(1) .onErrorResumeNext(Observable.empty()) .defaultIfEmpty("Oups !"); return Observable.zip(complex, human, (c, h) -> c + h) .flatMap(str -> webService.frequencyOf(str)) .flatMapIterable(itr -> itr); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .flatMap(i -> {
Observable<String> complex = webService.complexify(i) .map(str -> str.toLowerCase()) .retry(4); Observable<String> human = webService.toHumanString(i) .filter(str -> str.length() < 3) .take(1) .onErrorResumeNext(Observable.empty()) .defaultIfEmpty("Oups !"); return Observable.zip(complex, human, (c, h) -> c + h) .flatMap(str -> webService.frequencyOf(str)) .flatMapIterable(itr -> itr); }).subscribe(); Trop complexe ! -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .flatMap(i -> {
Observable<String> complex = webService.complexify(i) .map(str -> str.toLowerCase()) .retry(4); Observable<String> human = webService.toHumanString(i) .filter(str -> str.length() < 3) .take(1) .onErrorResumeNext(Observable.empty()) .defaultIfEmpty("Oups !"); return Observable.zip(complex, human, (c, h) -> c + h) .flatMap(str -> webService.frequencyOf(str)) .flatMapIterable(itr -> itr); }).subscribe(); Extraction -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .flatMap(i -> transform(i))
.subscribe(); public Observable<Object> transform(int i) { Observable<String> complex = webService.complexify(i) .map(str -> str.toLowerCase()) .retry(4); Observable<String> human = webService.toHumanString(i) .filter(str -> str.length() < 3) .take(1) .onErrorResumeNext(Observable.empty()) .defaultIfEmpty("Oups !"); return Observable.zip(complex, human, (c, h) -> c + h) .flatMap(str -> webService.frequencyOf(str)) .flatMapIterable(itr -> itr); } -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .flatMap(i -> transform(i))
.subscribe(); public Observable<Object> transform(int i) { Observable<String> complex = fetchComplexify(i); Observable<String> human = fetchHuman(i); return Observable.zip(complex, human, (c, h) -> c + h) .flatMap(str -> webService.frequencyOf(str)) .flatMapIterable(itr -> itr) } -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ne quittez pas la monade !
Observable.just(1, 2, 3) .subscribe(arg -> { webservice.call(arg).subscribe(System.out::println); }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.just(1, 2, 3) .subscribe(arg -> {
webservice.call(arg).subscribe(System.out::println); }); Ne quittez pas la monade ! Observable imbriqué -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ne quittez pas la monade !
Observable.just(1, 2, 3) .flatMap(arg -> webservice.call(arg)) .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Ne quittez pas la monade !
Observable.just(1, 2, 3) .flatMap(arg -> webservice.call(arg)) .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx flatMap permet d'émettre zéro, un, ou
plusieurs évènements -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(tps -> Observable.just(1, 2,
3)) .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(tps -> Observable.just(1, 2,
3)) .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(tps -> Observable.just(1, 2,
3)) .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(tps -> Observable.just(1, 2,
3)) .subscribe(System.out::println, Throwable::printStackTrace, () -> System.out.println("Completed !")); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(tps -> Observable.just(1, 2,
3)) .subscribe(System.out::println, Throwable::printStackTrace, () -> System.out.println("Completed !")); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(tps -> Observable.just(1, 2,
3)) .subscribe(System.out::println, Throwable::printStackTrace, () -> System.out.println("Completed !")); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(tps -> Observable.just(1, 2,
3)) .subscribe(System.out::println, Throwable::printStackTrace, () -> System.out.println("Completed !")); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(tps -> Observable.just(1, 2,
3)) .subscribe(System.out::println, Throwable::printStackTrace, () -> System.out.println("Completed !")); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(tps -> Observable.just(1, 2,
3)) .subscribe(System.out::println, Throwable::printStackTrace, () -> System.out.println("Completed !")); Cela n’arrivera jam ais ! -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Il faut comprendre le contrat Rx
pour comprendre les opérateurs -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { Timer timer =
new Timer(); timer.onTick(() -> subscriber.onNext("tick")); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { Timer timer =
new Timer(); timer.onTick(() -> subscriber.onNext("tick")); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { Timer timer =
new Timer(); timer.onTick(() -> subscriber.onNext("tick")); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { Timer timer =
new Timer(); timer.onTick(() -> subscriber.onNext("tick")); }).subscribe(); Cold -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Timer timer = new Timer(); Observable.create(subscriber
-> { timer.onTick(() -> subscriber.onNext("tick")); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Timer timer = new Timer(); Observable.create(subscriber
-> { timer.onTick(() -> subscriber.onNext("tick")); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Timer timer = new Timer(); Observable.create(subscriber
-> { timer.onTick(() -> subscriber.onNext("tick")); }).subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Timer timer = new Timer(); Observable.create(subscriber
-> { timer.onTick(() -> subscriber.onNext("tick")); }).subscribe(); Hot -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable<String> response = ws.call(); response .map(str
-> str.toLowerCase()) .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable<String> response = ws.call(); response .map(str
-> str.toLowerCase()) .subscribe(System.out::println); response .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable<String> response = ws.call(); response .map(str
-> str.toLowerCase()) .subscribe(System.out::println); response .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable<String> response = ws.call(); response .map(str
-> str.toLowerCase()) .subscribe(System.out::println); response .subscribe(System.out::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable<String> response = ws.call(); response .map(str
-> str.toLowerCase()) .subscribe(System.out::println); response .subscribe(System.out::println); Cold -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable<String> response = ws.call(); response .map(str
-> str.toLowerCase()) .subscribe(System.out::println); response .subscribe(System.out::println); Cold Création d’un appel réseau -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable<String> response = ws.call(); response .map(str
-> str.toLowerCase()) .subscribe(System.out::println); response .subscribe(System.out::println); Cold Création d’un second appel réseau Création d’un appel réseau -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Utilisation de MDC pour les logs
: attention aux mauvaises surprises. -
@BriceDutheil @dwursteisen #DevoxxFR #Rx WARN AAA [2014-11-10 02:57:24,566] first_observable: 9
MDC.put("ID", "AAA"); LOG.warning("first_observable: {}", l); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx WARN AAA [2014-11-10 02:57:24,566] first_observable: 9
MDC.put("ID", "AAA"); LOG.warning("first_observable: {}", l); Utilisation de ThreadLocal -
@BriceDutheil @dwursteisen #DevoxxFR #Rx WARN AAA [2014-11-10 02:57:24,566] first_observable: 9
WARN BBB [2014-11-10 02:57:24,576] second_observable: 1 WARN AAA [2014-11-10 02:57:25,566] first_observable: 10 WARN CCC [2014-11-10 02:57:25,576] third_observable: 2 Contexte synchrone -
@BriceDutheil @dwursteisen #DevoxxFR #Rx WARN AAA [2014-11-10 02:57:24,566] first_observable: 9
WARN BBB [2014-11-10 02:57:24,576] second_observable: 1 WARN AAA [2014-11-10 02:57:25,566] first_observable: 10 WARN CCC [2014-11-10 02:57:25,576] third_observable: 2 Contexte synchrone -
@BriceDutheil @dwursteisen #DevoxxFR #Rx WARN AAA [2014-11-10 02:57:24,566] first_observable: 9
WARN BBB [2014-11-10 02:57:24,576] third_observable: 1 WARN AAA [2014-11-10 02:57:25,566] second_observable: 10 WARN BBB [2014-11-10 02:57:25,576] third_observable: 2 Contexte asynchrone -
@BriceDutheil @dwursteisen #DevoxxFR #Rx WARN AAA [2014-11-10 02:57:24,566] first_observable: 9
WARN BBB [2014-11-10 02:57:24,576] third_observable: 1 WARN AAA [2014-11-10 02:57:25,566] second_observable: 10 WARN BBB [2014-11-10 02:57:25,576] third_observable: 2 Contexte asynchrone Interleaving -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Il est nécessaire de remettre en
place le contexte lors d’un changement de contexte. -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() { @Override public Action0
onSchedule(Action0 action) { return super.onSchedule(() -> { MDC.setContextMap(mdcContextMap); try { action.call(); } finally { MDC.clear(); } }); } }); http://blog.mabn.pl/2014/11/rxjava-logback-and-mdc-threadlocal.html (version courte) -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() { @Override public Action0
onSchedule(Action0 action) { return super.onSchedule(() -> { MDC.setContextMap(mdcContextMap); try { action.call(); } finally { MDC.clear(); } }); } }); http://blog.mabn.pl/2014/11/rxjava-logback-and-mdc-threadlocal.html (version courte) À chaque planification de tâche -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() { @Override public Action0
onSchedule(Action0 action) { return super.onSchedule(() -> { MDC.setContextMap(mdcContextMap); try { action.call(); } finally { MDC.clear(); } }); } }); http://blog.mabn.pl/2014/11/rxjava-logback-and-mdc-threadlocal.html (version courte) À chaque planification de tâche On planifie une autre tâche -
@BriceDutheil @dwursteisen #DevoxxFR #Rx RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() { @Override public Action0
onSchedule(Action0 action) { return super.onSchedule(() -> { MDC.setContextMap(mdcContextMap); try { action.call(); } finally { MDC.clear(); } }); } }); http://blog.mabn.pl/2014/11/rxjava-logback-and-mdc-threadlocal.html (version courte) À chaque planification de tâche On planifie une autre tâche Qui remet le contexte et qui exécute notre tâche -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(i -> { try
{ RandomException.mayThrowException(); return Observable.empty(); } catch (IOException e) { return Observable.error(e); } }).subscribe(System.out::println, e -> { /* ... */ }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(i -> { try
{ RandomException.mayThrowException(); return Observable.empty(); } catch (IOException e) { return Observable.error(e); } }).subscribe(System.out::println, e -> { /* ... */ }); Quelle valeur a généré l’exception ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(i -> { try
{ RandomException.mayThrowException(); return Observable.empty(); } catch (IOException e) { return Observable.error(OnErrorThrowable.addValueAsLastCause(e, i)); } }).subscribe(System.out::println, e -> { /* ... */ }); Associe l’exception avec une valeur -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.interval(1, TimeUnit.SECONDS) .flatMap(i -> { try
{ RandomException.mayThrowException(); return Observable.empty(); } catch (IOException e) { return Observable.error(OnErrorThrowable.addValueAsLastCause(e, i)); } }).subscribe(System.out::println, e -> { Object value = OnErrorThrowable.from(e).getValue(); }); Récupère la valeur depuis l’exception -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Grâce à OnErrorThrowable, il est possible
d'accéder à la valeur responsable de l’exception -
@BriceDutheil @dwursteisen #DevoxxFR #Rx public interface WebService { Observable<String> call();
} Va émettre une valeur ? Va émettre plusieurs valeurs ? Va émettre aucune valeur ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx public interface WebService { Observable<String> list();
Single<String> get(); Completable perform(); } Va émettre un ensemble de valeurs va émettre une unique valeur ne va pas émettre de valeur -
@BriceDutheil @dwursteisen #DevoxxFR #Rx La souscription permet de signaler le
désabonnement à un flux -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Subscription interval = Observable.interval(1, TimeUnit.SECONDS) .subscribe(System.out::println);
interval.unsubscribe(); Stop le flux -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Subscription flux1 = Observable.interval(1, TimeUnit.SECONDS) .subscribe(System.out::println);
Subscription flux2 = Observable.interval(100, TimeUnit.MILLISECONDS) .subscribe(System.err::println); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Subscription flux1 = Observable.interval(1, TimeUnit.SECONDS) .subscribe(System.out::println);
Subscription flux2 = Observable.interval(100, TimeUnit.MILLISECONDS) .subscribe(System.err::println); flux1.unsubscribe(); flux2.unsubscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Subscription flux1 = Observable.interval(1, TimeUnit.SECONDS) .subscribe(System.out::println);
Subscription flux2 = Observable.interval(100, TimeUnit.MILLISECONDS) .subscribe(System.err::println); CompositeSubscription subscription = new CompositeSubscription(); subscription.add(flux1); subscription.add(flux2); Compose les abonnements -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Subscription flux1 = Observable.interval(1, TimeUnit.SECONDS) .subscribe(System.out::println);
Subscription flux2 = Observable.interval(100, TimeUnit.MILLISECONDS) .subscribe(System.err::println); CompositeSubscription subscription = new CompositeSubscription(); subscription.add(flux1); subscription.add(flux2); subscription.unsubscribe(); Désabonne les flux -
@BriceDutheil @dwursteisen #DevoxxFR #Rx private CompositeSubscription subscription; @Override public void
onResume() { Subscription interval = Observable.interval(1, TimeUnit.SECONDS) .subscribe(/* ... */); Subscription click = clicks() .subscribe(/*...*/); subscription = new CompositeSubscription(); subscription.add(interval); subscription.add(click); } @Override public void onPause() { subscription.unsubscribe(); } Désabonne les flux -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { subscriber.add(new Subscription() {
@Override public void unsubscribe() { // fermeture du flux } }); }); Fermeture des ressources, etc -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.create(subscriber -> { subscriber.add(new Subscription() {
@Override public void unsubscribe() { // fermeture du flux } }); }); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Mais comment le faire sans utiliser
Observable.create ? -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.using( () -> new DB(), db
-> Observable.just(db.doQuery()), db -> db.closeDb()) .subscribe(); -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Observable.using( () -> new DB(), db
-> Observable.just(db.doQuery()), db -> db.closeDb()) .subscribe(); Fermeture des ressources, etc -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Les autres talks sur les Reactive
Extensions - A lite Rx API for the JVM Hands On Labs - Mercredi 9h30 - Baptême du feu avec Rx Hands On Labs - Vendredi 11h00 Alexandre Victoor Florent Le Gall Sebastien Deleuze Stephane Maldini -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Remerciements Gautier Mechling @Nilhcem Simon Baslé
@simonbasle David Karnok @akarnokd Dan Lew @danlew42 -
@BriceDutheil @dwursteisen #DevoxxFR #Rx Y a t’il des points que
nous n’aurions pas abordés ?