Reactive Streams
16/12/2020
Reactive Manifesto
Reactive Manifesto
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
Reactive Manifesto
ELASTIC
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
Reactive systems can react to changes and stay responsive under varying workload
Reactive Manifesto
ELASTIC
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
In case the system faces any failure, the system should stay responsive
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
Reactive systems can react to changes and stay responsive under varying workload
RESILIENT
Reactive Manifesto
ELASTIC
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
In case the system faces any failure, the system should stay responsive
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
Reactive systems can react to changes and stay responsive under varying workload
RESILIENT
MESSAGE DRIVEN
Message passing between systems ensure loose coupling and isolation
JDK9 java.util.concurrent.Flow
asynchronous stream processing with non-blocking backpressure
Asynchronous ≠ Non-Blocking
Asynchronous
Jean-Mich ask something to Didier, Didier is not able to answer now but Didier will call him back when he will have the answer
Non-Blocking
Jean-Mich ask something to Didier, Didier is not able to answer. Jean-Mich has to call back Didier some time after to get the response.
Backpressure

Backpressure

Backpressure

Problem : When the Consumer cannot handle the throughput provided by the Publisher
Backpressure
=
Subscriber can control the Publisher throughput
JDK9 java.util.concurrent.Flow
A Subscriber subscribe to a Publisher
3 interfaces :
- Publisher
- Subscriber
- Suscription
Concurrency-agnostic
JDK9 java.util.concurrent.Flow
public interface Publisher<T> {
void subscribe(Subscriber<? super T> s);
}
public interface Subscriber<T> {
void onSubscribe(Subscription s);
void onNext(T t);
void onError(Throwable t);
void onComplete();
}
public interface Subscription {
public void request(long n);
public void cancel();
}
Implement Flow API
Array Publisher
new ArrayPublisher<>(List.of("Pikachu","Salameche","Bulbizarre","Carapuce"))
.subscribe(new Flow.Subscriber<>() {...});
/*
01:02:14.367 [main] INFO com.tahitiste.reactive.ArrayPublisherTest - onNext : Pikachu
01:02:14.370 [main] INFO com.tahitiste.reactive.ArrayPublisherTest - onNext : Salameche
01:02:14.370 [main] INFO com.tahitiste.reactive.ArrayPublisherTest - onNext : Bulbizarre
01:02:14.370 [main] INFO com.tahitiste.reactive.ArrayPublisherTest - onNext : Carapuce
01:02:14.370 [main] INFO com.tahitiste.reactive.ArrayPublisherTest - Complete !
*/
Implement Flow APi
Interval Publisher
new ColdIntervalPublisher(Duration.ofSeconds(1)).subscribe(new Flow.Subscriber<>() {...});
/* prints :
01:05:01.256 [pool-1-thread-1] INFO com.tahitiste.reactive.IntervalPublisherTest - tick received
01:05:02.252 [pool-1-thread-1] INFO com.tahitiste.reactive.IntervalPublisherTest - tick received
01:05:03.252 [pool-1-thread-1] INFO com.tahitiste.reactive.IntervalPublisherTest - tick received
01:05:04.252 [pool-1-thread-1] INFO com.tahitiste.reactive.IntervalPublisherTest - tick received
01:05:05.252 [pool-1-thread-1] INFO com.tahitiste.reactive.IntervalPublisherTest - tick received
01:05:06.252 [pool-1-thread-1] INFO com.tahitiste.reactive.IntervalPublisherTest - tick received
01:05:07.252 [pool-1-thread-1] INFO com.tahitiste.reactive.IntervalPublisherTest - tick received
*/
Improve Flow APi
Map/Filter operators
new ArrayPublisher<>("Alex","Manu","Yunus","Sylvain")
.filter(this::isMeilleurCoderDeFrance)
.subscribe(new Flow.Subscriber<>() {...});
new ArrayPublisher<>(List.of("ho", "hi", "ca", "cu", "pi"))
.map(value -> value + value)
.subscribe(new Flow.Subscriber<String>() {...});
Flow API implementation
Project Reactor

Project Reactor
- Maintained by Pivotal, firm behind Spring
- Reactor Netty for TCP/HTTP (Server/Client)
- Used by Spring in its reactive stack :
- Spring WebFlux (equivalent of Spring MVC)
- Spring Data (Mongo,Redis,R2DBC)
- Reactor for Kafka
java.Flow => Project Reactor
java.util.concurrent.Flow | reactor.core |
---|---|
N values Publisher | Flow |
1 value Publisher | Mono |
No need to implement Subscriber/Subscription
java.Flow => Project Reactor
-
Composability and readability
-
Data as a flow manipulated with a rich vocabulary of operators
-
Nothing happens until you subscribe
-
Backpressure or the ability for the consumer to signal the producer that the rate of emission is too high
-
High level but high value abstraction that is concurrency-agnostic
Project Reactor : construct
Text
Mono<String> emptyMono = Mono.empty();
Flux<String> emptyFlux = Flux.empty();
Mono<String> mono = Mono.just("Bioserenity");
Flux<String> flux = Flux.just("PSG", "Barca", "Liverpool");
Flux.range(1, 100); // from 1 to 100
Flux.fromStream(Stream.of("PSG", "Barca", "Liverpool"));
Flux.fromIterable(List.of("PSG", "Barca", "Liverpool"));
Mono<String> stringMono = Mono.fromCallable(() -> "hello");
Mono<Void> voidMono = Mono.fromRunnable(() -> System.out.println("Hello"));
Mono<String> completableFutureMono = Mono.fromFuture(new CompletableFuture<>());
Project Reactor : transform/combine
flux.map(...)
mono.map(...)
flux.filter(...)
mono.filter(...)
Demo in previous implementation
Project Reactor : transform/combine
flux.map(...)
mono.map(...)
flux.filter(...)
mono.filter(...)
Project Reactor : transform/combine
flux.map(...)
mono.map(...)
flux.filter(...)
mono.filter(...)
Flux<Event> eventList;
Mono<List<Event>> collectedEventList = eventList.collectList();
Project Reactor : transform/combine
flux.map(...)
mono.map(...)
flux.filter(...)
mono.filter(...)
Flux<Event> bffEvents = getBffEvents();
Flux<Event> mortaraEvents = getMortaraEvents();
Flux<Event> mergedEvents = Flux.merge(bffEvents,mortaraEvents);
Flux<Event> eventList;
Mono<List<Event>> collectedEventList = eventList.collectList();
Project Reactor : transform/combine
flux.map(...)
mono.map(...)
flux.filter(...)
mono.filter(...)
Flux<User> userListToSave;
Flux<SavedUser> saveUserList = userListToSave.flatMap(user -> saveUser(user));
Mono<User> user = getUser(userId)
Flux<Record> userRecordList = user.flatMapMany(user -> getRecords(user));
Flux<Event> bffEvents = getBffEvents();
Flux<Event> mortaraEvents = getMortaraEvents();
Flux<Event> mergedEvents = Flux.merge(bffEvents,mortaraEvents);
Flux<Event> eventList;
Mono<List<Event>> collectedEventList = eventList.collectList();
Project Reactor : Subscribe
Flux.just(1, 2, 3).subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription subscription) {
subscription.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer integer) {
LOGGER.info("{}", integer);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
LOGGER.info("complete ! ");
}
});
Project Reactor : Subscribe
Flux.just(1, 2, 3).subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription subscription) {
subscription.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer integer) {
LOGGER.info("{}", integer);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
LOGGER.info("complete ! ");
}
});
Flux.just(1, 2, 3).subscribe(integer -> LOGGER.info("{}", integer));
Flux.just(1, 2, 3).subscribe(
integer -> LOGGER.info("{}", integer),
error -> LOGGER.error("{}", error),
() -> LOGGER.info("complete !")
);
Project Reactor :
do side effects
LOGGER.info("Start");
Flux.just(1, 2, 3)
.doOnNext(integer -> LOGGER.info("side effect : {}", integer))
.doFirst(() -> LOGGER.info("first side effect"))
.doOnComplete(() -> LOGGER.info("complete "))
.subscribe();
/* LOGS
16:02:43.472 [main] INFO com.tahitiste.reactive.FluxTest - Start
16:02:43.551 [main] INFO com.tahitiste.reactive.FluxTest - first side effect
16:02:43.553 [main] INFO com.tahitiste.reactive.FluxTest - side effect : 1
16:02:43.554 [main] INFO com.tahitiste.reactive.FluxTest - side effect : 2
16:02:43.554 [main] INFO com.tahitiste.reactive.FluxTest - side effect : 3
16:02:43.554 [main] INFO com.tahitiste.reactive.FluxTest - complete
*/
Simple example :
Create a load test
int numberOfRequestToRun = 1000;
int numberOfConcurrentRequest = 100;
Flux<Mono<String>> requests = Flux
.range(0, numberOfRequestToRun)
.map(unused -> httpClient // <= Netty reactive HTTPClient
.get()
.uri("uri")
.responseContent()
.aggregate()
.asString()
);
// we run concurrently
Flux<String> responseFlux = Flux.merge(requests, numberOfConcurrentRequest);
//
List<String> responses = responseFlux.collectList().block();
Concurrency
JDK Flow / Reactor
are
concurrency-agnostic
LOGGER.info("Start");
Flux.just(1, 2, 3, 4, 5).subscribe(integer -> LOGGER.info("{}", integer));
/* logs :
15:55:30.361 [main] INFO com.tahitiste.reactive.FluxConstruction - Start
15:55:30.461 [main] INFO com.tahitiste.reactive.FluxConstruction - 1
15:55:30.462 [main] INFO com.tahitiste.reactive.FluxConstruction - 2
15:55:30.462 [main] INFO com.tahitiste.reactive.FluxConstruction - 3
15:55:30.462 [main] INFO com.tahitiste.reactive.FluxConstruction - 4
15:55:30.462 [main] INFO com.tahitiste.reactive.FluxConstruction - 5
*/
Concurrency
JDK Flow / Reactor
are
concurrency-agnostic
Concurrency
But Reactor has some helpers : block()
LOGGER.info("Start");
String value = Mono.just("hello").block();
LOGGER.info("{}", value);
int lastValue = Flux.just(1, 2, 3).blockLast();
LOGGER.info("{}", lastValue);
int firstValue = Flux.just(1, 2, 3).blockFirst();
LOGGER.info("{}", firstValue);
/* LOGS :
16:13:13.077 [main] INFO com.tahitiste.reactive.FluxTest - Start
16:13:13.126 [main] INFO com.tahitiste.reactive.FluxTest - hello
16:13:13.191 [main] INFO com.tahitiste.reactive.FluxTest - 3
16:13:13.191 [main] INFO com.tahitiste.reactive.FluxTest - 1
*/
// Its executed on the same thread !
Concurrency
But Reactor has some helpers : parallel()
LOGGER.info("start main");
Flux.range(1, 5)
.parallel(3)
.runOn(Schedulers.parallel())
.subscribe(i -> LOGGER.info("{}", i));
LOGGER.info("end main");
Thread.sleep(1000);
/* LOGS
16:49:03.333 [main] INFO com.tahitiste.reactive.FluxTest - start main
16:49:03.451 [main] INFO com.tahitiste.reactive.FluxTest - end main
16:49:03.451 [parallel-1] INFO com.tahitiste.reactive.FluxTest - 1
16:49:03.451 [parallel-2] INFO com.tahitiste.reactive.FluxTest - 2
16:49:03.451 [parallel-3] INFO com.tahitiste.reactive.FluxTest - 3
16:49:03.454 [parallel-2] INFO com.tahitiste.reactive.FluxTest - 5
16:49:03.454 [parallel-1] INFO com.tahitiste.reactive.FluxTest - 4
Concurrency
But Reactor has some helpers : subscribeOn()
Flux.just(1, 2, 3)
.subscribeOn(Schedulers.newSingle("executor"))
.subscribe(value -> LOGGER.info("{}", value));
/* LOGS :
16:15:11.833 [main] INFO com.tahitiste.reactive.FluxTest - Start
16:15:11.914 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework
16:15:11.932 [executor-1] INFO com.tahitiste.reactive.FluxTest - 1
16:15:11.934 [executor-1] INFO com.tahitiste.reactive.FluxTest - 2
16:15:11.934 [executor-1] INFO com.tahitiste.reactive.FluxTest - 3
*/
// Its executed on the executor thread !
When you want to subscribe on another thread
Concurrency
But Reactor has some helpers : publishOn()
Flux.just(1, 2, 3)
.publishOn(Schedulers.newSingle("executor"))
.subscribe(value -> LOGGER.info("{}", value));
/* LOGS :
16:15:11.833 [main] INFO com.tahitiste.reactive.FluxTest - Start
16:15:11.914 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework
16:15:11.932 [executor-1] INFO com.tahitiste.reactive.FluxTest - 1
16:15:11.934 [executor-1] INFO com.tahitiste.reactive.FluxTest - 2
16:15:11.934 [executor-1] INFO com.tahitiste.reactive.FluxTest - 3
*/
// Its executed on the executor thread !
When you want to publish on another thread
Concurrency
Blocking calls => warning !
LOGGER.info("Start");
Mono<String> blockingCall = Mono.fromRunnable(() -> {
try {
Thread.sleep(1000);
LOGGER.info("end of sleep");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
blockingCall.subscribe();
LOGGER.info("sleeping");
/* LOGS
16:30:51.377 [main] INFO com.tahitiste.reactive.FluxTest - Start
16:30:52.427 [main] INFO com.tahitiste.reactive.FluxTest - end of sleep
16:30:52.428 [main] INFO com.tahitiste.reactive.FluxTest - sleeping
main thread was blocked by blockingCall
*/
Concurrency
Blocking calls => execute call in scheduler
LOGGER.info("Start");
Mono<String> blockingCall = Mono.fromRunnable(() -> {
try {
Thread.sleep(1000);
LOGGER.info("end of sleep");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
blockingCall
.subscribeOn(Schedulers.single())
.subscribe();
LOGGER.info("sleeping");
/* LOGS
16:28:51.322 [main] INFO com.tahitiste.reactive.FluxTest - Start
16:28:51.388 [main] INFO com.tahitiste.reactive.FluxTest - sleeping
16:28:52.389 [single-1] INFO com.tahitiste.reactive.FluxTest - end of sleep
main thread was not blocked by blockingCall
*/
- Single thread => Scheduler.single()
- elastic thread pool => Schedulers.boundedElastic()
- fixed thread pool => Schedulers.parallel()
- Schedulers.fromExecutorService()
Concurrency
Concurrency
Performance ?
Concurrency
Performance ?
Not more performant than classic java code with
Threads / Future / ExecutorService / CompletableFuture / ...
=> because it uses these concepts under the hood
Concurrency
Performance ?
Not more performant than classic java code with
Threads / Future / ExecutorService / CompletableFuture / ...
=> because it uses these concepts under the hood
But it provide tools to made asynchronous programming easier
Backpressure ?
public interface Subscription {
public void request(long n);
public void cancel();
}
Backpressure ?
public interface Subscription {
public void request(long n);
public void cancel();
}
- buffer() => buffer elements
- onBackpressureDrop() => publisher drop if no enough request
- ...
Logging : wait what ?
Mono<String> getComputation() {
LOGGER.info("start Computation");
return Mono.just("hello !");
}
public runComputation {
Mono<String> computationMono = getComputation();
Thread.sleep(1000);
LOGGER.info("subscribe computation");
computationMono.doOnNext(value -> LOGGER.info("{}", value))
.subscribe();
Thread.sleep(10000);
}
/* LOGS
17:12:15.814 [main] INFO com.tahitiste.reactive.FluxTest - start Computation
17:12:16.862 [main] INFO com.tahitiste.reactive.FluxTest - subscribe computation
17:12:16.869 [main] INFO com.tahitiste.reactive.FluxTest - hello !
getComputation log is shown before the effective subscription of the computation
*/
Logging : wait what ?
Mono<String> getComputation() {
return Mono.fromRunnable(() -> LOGGER.info("start Computation"))
.then(Mono.just("hello !"));
}
public runComputation {
Mono<String> computationMono = getComputation();
Thread.sleep(1000);
LOGGER.info("subscribe computation");
computationMono.doOnNext(value -> LOGGER.info("{}", value))
.subscribe();
Thread.sleep(10000);
}
/* LOGS
17:10:41.131 [main] INFO com.tahitiste.reactive.FluxTest - subscribe computation
17:10:41.142 [main] INFO com.tahitiste.reactive.FluxTest - start Computation
17:10:41.142 [main] INFO com.tahitiste.reactive.FluxTest - hello !
getComputation log is shown after the effective subscription of the computation
*/
Tests
Flux<String> computation = getComputation();
String actualResult = computation.block();
String expectedResult = "expected";
assertEquals(expectedResult,actualResult);
Make blocking code ;)
Tests
Flux<String> computation = getComputation();
String actualResult = computation.block();
String expectedResult = "expected";
assertEquals(expectedResult,actualResult);
Make blocking code ;)
For more control : StepVerifier
Flux<String> flux = Flux.just("Max","Alex","Yunus");
StepVerifier.create(flux)
.expectNext("Max")
.expectNextCount(2)
.expectComplete()
.verify();
Conclusion
- Concurrency is simple : no more callback / no more future.get()
- Composition and readability
- Think from the start about communication issues : back-pressure, retry, buffer
Conclusion
- Concurrency is simple : no more callback / no more future.get()
- Composition and readability
- Think from the start about communication issues : back-pressure, retry, buffer
BUT
- less simple than classic imperative programming
- Another framework to learn
- Use it once, use it everywhere
Reactive Stream
By maximeleprince
Reactive Stream
- 187