16/12/2020
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
RESPONSIVE
Responsive systems focus on providing rapid and consistent response times
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
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
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
asynchronous stream processing with non-blocking backpressure
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.
Problem : When the Consumer cannot handle the throughput provided by the Publisher
Backpressure
=
Subscriber can control the Publisher throughput
A Subscriber subscribe to a Publisher
3 interfaces :
Concurrency-agnostic
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();
}
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 !
*/
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
*/
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>() {...});
java.util.concurrent.Flow | reactor.core |
---|---|
N values Publisher | Flow |
1 value Publisher | Mono |
No need to implement Subscriber/Subscription
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
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<>());
flux.map(...)
mono.map(...)
flux.filter(...)
mono.filter(...)
Demo in previous implementation
flux.map(...)
mono.map(...)
flux.filter(...)
mono.filter(...)
flux.map(...)
mono.map(...)
flux.filter(...)
mono.filter(...)
Flux<Event> eventList;
Mono<List<Event>> collectedEventList = eventList.collectList();
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();
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();
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(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 !")
);
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
*/
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();
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
*/
JDK Flow / Reactor
are
concurrency-agnostic
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 !
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
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
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
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
*/
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
*/
Not more performant than classic java code with
Threads / Future / ExecutorService / CompletableFuture / ...
=> because it uses these concepts under the hood
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
public interface Subscription {
public void request(long n);
public void cancel();
}
public interface Subscription {
public void request(long n);
public void cancel();
}
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
*/
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
*/
Flux<String> computation = getComputation();
String actualResult = computation.block();
String expectedResult = "expected";
assertEquals(expectedResult,actualResult);
Make blocking code ;)
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();
BUT