Reactive Streams in the Web
Florian Stefan | eBay Classifieds Group | JUG Saxony 2017
Who am I?
§ Florian Stefan
§ mobile.de (eBay Classifieds Group)
§ https://ebaytech.berlin/
§ fstefan@ebay.com | @f_s_t_e_f_a_n
§ https://github.com/florian-stefan/
Contents
1) Motivation: Distributed Systems
2) Reactive Streams with Reactor
3) Progressive HTML Rendering with Spring 5
It‘s about concepts rather than details!
Why are we here?
Why are we here?
It’s a distributed world!
Why are we here?
Service A Service B
Why are we here?
Service A Service B
Service B
Service B
Service A
Service A
Why are we here?
Service A Service B
Service B
Service B
Service A
Service A
Why are we here?
Service A Service B
BFF
BFF
Service B
Service B
Service A
Service A
Why are we here?
Service A Service B
BFF
BFF
BFF
Service B
Service B
Service A
Service A
Why are we here?
Service A Service B
BFF
How are we doing this?
How are we doing this?
Service A
How are we doing this?
Service A
Database
HTTP
How are we doing this?
Service A
Database
HTTP
How are we doing this?
Service A
Database
Topic
HTTP
How are we doing this?
Service A
Database
Topic
HTTP
How are we doing this?
Service A
Database
Topic Service B Topic
HTTP
How are we doing this?
Service A
Database
Topic Service B Topic
BFF
HTTP
How are we doing this?
Service A
Database
Topic Service B Topic
BFF
Event-Driven Systems
HTTP
How are we doing this?
Service A
Database
Topic Service B Topic
BFF
Event-Driven Systems
Concurrent Remote Calls
HTML Rendering
BFF
Widget Widget
Widget
Widget Widget
Concurrent Remote Calls
HTML Rendering
BFF
Widget Widget
Widget
Widget Widget
Concurrent Remote Calls
HTML Rendering
BFF
Widget Widget
Widget
Widget Widget
Concurrent Remote Calls
HTML Rendering
Server-Side Rendering
BFF
Widget Widget
Widget
Widget Widget
Concurrent Remote Calls
HTML Rendering
Server-Side Rendering
AJAX
BFF
Widget Widget
Widget
Widget Widget
Concurrent Remote Calls
HTML Rendering
HTML Rendering
HTML Rendering
HTML Rendering
Server-Side Rendering
§ Time To First Byte
§ Prerendering / Caching
HTML Rendering
Server-Side Rendering
§ Time To First Byte
§ Prerendering / Caching
HTML Rendering
HTML Rendering
HTML Rendering
HTML Rendering
HTML Rendering
AJAX
§ Head-of-line blocking
§ Multiplexing with HTTP/2
HTML Rendering
AJAX
§ Head-of-line blocking
§ Multiplexing with HTTP/2
HTML Rendering
Can we do better?
Can we do better?
How expensive will it be?
The Reactive Landscape
Distributed
Systems
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Streams
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Streams
Backpressure
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Streams
Reactive
Extensions
Akka Streams
Backpressure
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Streams
Reactive
Extensions
RxJava
Reactor
Akka Streams
Backpressure
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Streams
Reactive
Extensions
Marble
Diagrams
RxJava
Reactor
Akka Streams
Backpressure
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Streams
Reactive
Extensions
Marble
Diagrams
RxJava
Reactor
Akka Streams
Backpressure
Higher-Order
Functions
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Streams
Reactive
Extensions
Marble
Diagrams
RxJava
Reactor
Akka Streams
Backpressure
Declarative
Programming
Higher-Order
Functions
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Systems
Reactive
Streams
Reactive
Extensions
Marble
Diagrams
RxJava
Reactor
Akka Streams
Backpressure
Declarative
Programming
Higher-Order
Functions
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Systems
Reactive
Manifesto
Reactive
Streams
Reactive
Extensions
Marble
Diagrams
RxJava
Reactor
Akka Streams
Backpressure
Declarative
Programming
Higher-Order
Functions
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Systems
Reactive
Manifesto
Reactive
Streams
Reactive
Extensions
Marble
Diagrams
RxJava
Reactor
Akka Streams
Backpressure
Declarative
Programming
Higher-Order
Functions
The Reactive Manifesto
Responsive
Responsive
Elastic
Responsive
Elastic Resilient
Responsive
Elastic Resilient
Message Driven
Responsive
Elastic Resilient
Message Driven
React to User
Responsive
Elastic Resilient
Message Driven
React to User
React to Load
Responsive
Elastic Resilient
Message Driven
React to User
React to Load React to Failure
Responsive
Elastic Resilient
Message Driven
React to User
React to Load React to Failure
React to Events
Paths to Reactive Systems
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Systems
Reactive
Manifesto
Reactive
Streams
Reactive
Extensions
Marble
Diagrams
RxJava
Reactor
Akka Streams
Backpressure
Declarative
Programming
Higher-Order
Functions
Reactive Extensions
Reactive Extensions
Service
Reactive Extensions
Service
some
thing
Reactive Extensions
API
Service
some
thing
public interface Somethings {
Something loadById(int id);
}
Reactive Extensions
API
Service
some
thing
Reactive Extensions
API
Service
some
thing
public Something loadById(int id) {
return somethings.loadById(id);
}
Reactive Extensions
API
Service
some
thing
Service
Reactive Extensions
API
Service
some
thing
Service API
Reactive Extensions
API
Service
some
thing
Service API Resource
Reactive Extensions
API
Service
some
thing
Service API Resource
Network Boundary
Reactive Extensions
API
Service
some
thing
Service API Resource
Network Boundary
Reactive Extensions
API
Service
some
thing
Service API Resource
Network Boundary
Reactive Extensions
API
Service
some
thing
Service API Resource
Network Boundary
Reactive Extensions
API
Service
some
thing
Service API Resource
Network Boundary
Reactive Extensions
API
Service
some
thing
Reactive Extensions
API
Service
some
thing
public CompletableFuture<Something> loadById(int id) {
return supplyAsync(() -> somethings.loadById(id), executor);
}
Reactive Extensions
API
Service
some
thing
Network Boundary
ResourceService API
Reactive Extensions
API
Service
some
thing
Network Boundary
ResourceService API
Reactive Extensions
API
Service
some
thing
Network Boundary
ResourceService API
Reactive Extensions
API
Service
some
thing
Network Boundary
ResourceService API
Reactive Extensions
API
Service
some
thing
public interface Somethings {
void loadById(int id, BiConsumer<Exception, Something> callback);
}
Reactive Extensions
API
Service
some
thing
public CompletableFuture<Something> loadById(int id) {
CompletableFuture<Something> eventualSomething =
new CompletableFuture<>();
somethings.loadById(id, (error, something) -> {
if (error != null) {
eventualSomething.completeExceptionally(error);
} else {
eventualSomething.complete(something);
}
});
return eventualSomething;
}
Reactive Extensions
API
Service
some
thing
Network Boundary
ResourceService API
Reactive Extensions
API
Service
some
thing
Network Boundary
ResourceService API
Reactive Extensions
API
Service
some
thing
Network Boundary
ResourceService API
Reactive Extensions
API
Service
some
thing
Network Boundary
ResourceService API
Reactive Extensions
API
Service
some
thing
Reactive Extensions
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
subscribe { , }✘
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
subscribe { , }✘
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
subscribe { , }✘
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
subscribe { , }✘
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
subscribe { , }✘
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
subscribe { , }✘
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
subscribe { , }✘
Reactive Extensions
CompletableFuture<T>
represents an already
scheduled task
completes only once
Flux<T>
represents a building plan
(caller has to subscribe)
emits many values
Mono<T>
represents a building plan
(caller has to subscribe)
emits at most one value
subscribe { , }✘
Reactive Extensions
Publisher
Reactive Extensions
fromArray { }
[ , , ]
Operator
Publisher
Reactive Extensions
map { }
fromArray { }
[ , , ]
Operator
Subscriber
Publisher
Reactive Extensions
map { }
subscribe { }
fromArray { }
[ , , ]
Operator
Subscriber
Publisher
Reactive Extensions
map { }
subscribe { }
fromArray { }
[ , , ]
wraps
Operator
Subscriber
Publisher
Reactive Extensions
map { }
subscribe { }
fromArray { }
[ , , ]
wraps
Operator
Subscriber
Publisher
Reactive Extensions
map { }
subscribe { }
fromArray { }
[ , , ]
wraps
subscribes to
Reactive Extensions
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
.map(circle -> Square.of(circle.getColor()))
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
.map(circle -> Square.of(circle.getColor()))
.log()
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
.map(circle -> Square.of(circle.getColor()))
.subscribe(...);
.log()
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
.map(circle -> Square.of(circle.getColor()))
.subscribe(...);
.log()
[main] INFO - | onSubscribe()
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
.map(circle -> Square.of(circle.getColor()))
.subscribe(...);
.log()
[main] INFO - | onSubscribe()
[main] INFO - | ?
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
.map(circle -> Square.of(circle.getColor()))
.subscribe(...);
.log()
[main] INFO - | onSubscribe()
[main] INFO - | ?
[main] INFO - | onNext(Square(BLUE))
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
.map(circle -> Square.of(circle.getColor()))
.subscribe(...);
.log()
[main] INFO - | onSubscribe()
[main] INFO - | ?
[main] INFO - | onNext(Square(BLUE))
[main] INFO - | onNext(Square(YELLOW))
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
.map(circle -> Square.of(circle.getColor()))
.subscribe(...);
.log()
[main] INFO - | onSubscribe()
[main] INFO - | ?
[main] INFO - | onNext(Square(BLUE))
[main] INFO - | onNext(Square(YELLOW))
[main] INFO - | onNext(Square(GREEN))
Reactive Extensions
Circle[] circles = { Circle.of(BLUE), ... };
Flux.fromArray(circles)
.map(circle -> Square.of(circle.getColor()))
.subscribe(...);
.log()
[main] INFO - | onSubscribe()
[main] INFO - | ?
[main] INFO - | onNext(Square(BLUE))
[main] INFO - | onNext(Square(YELLOW))
[main] INFO - | onNext(Square(GREEN))
[main] INFO - | onComplete()
Concurrency
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Systems
Reactive
Manifesto
Reactive
Streams
Reactive
Extensions
Marble
Diagrams
RxJava
Reactor
Akka Streams
Backpressure
Declarative
Programming
Higher-Order
Functions
Concurrency Calling Thread
map { }
Concurrency
subscribe { }
Calling Thread
map { }
Concurrency
subscribe { }
Calling Thread
map { }
Concurrency
subscribe { }
Calling Thread
map { }
Concurrency
subscribe { }
Calling Thread
map { }
Concurrency
subscribe { }
Calling Thread
map { }
Concurrency
subscribe { }
Calling Thread
map { }
Concurrency
subscribe { }
Calling Thread
map { }
Concurrency Calling Thread
map { }
Concurrency Calling Thread Scheduler
subscribeOn { }
map { }
Concurrency
subscribe { }
Calling Thread Scheduler
subscribeOn { }
map { }
Concurrency
subscribe { }
Calling Thread Scheduler
subscribeOn { }
map { }
Concurrency
subscribe { }
Calling Thread Scheduler
subscribeOn { }
map { }
Concurrency
subscribe { }
Calling Thread Scheduler
subscribeOn { }
map { }
Concurrency
subscribe { }
Calling Thread Scheduler
subscribeOn { }
map { }
Concurrency
subscribe { }
Calling Thread Scheduler
subscribeOn { }
map { }
Concurrency
subscribe { }
Calling Thread Scheduler
subscribeOn { }
map { }
Concurrency Calling Thread
flatMap { }
Concurrency Calling Thread Scheduler
flatMap { }
subscribeOn { }
Concurrency
subscribe { }
Calling Thread Scheduler
flatMap { }
subscribeOn { }
Concurrency
subscribe { }
Calling Thread Scheduler
flatMap { }
subscribeOn { }
Concurrency
subscribe { }
Calling Thread Scheduler
flatMap { }
subscribeOn { }
Concurrency
subscribe { }
Calling Thread Scheduler
flatMap { }
subscribeOn { }
Concurrency
subscribe { }
Calling Thread Scheduler
flatMap { }
subscribeOn { }
Concurrency
subscribe { }
Calling Thread Scheduler
flatMap { }
subscribeOn { }
Concurrency
subscribe { }
Calling Thread Scheduler
flatMap { }
subscribeOn { }
Concurrency
Concurrency
API
Service
some
thing
Concurrency
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler))
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler))
.log()
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler))
.log()
.subscribe();
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
[main] INFO - onSubscribe()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler))
.log()
.subscribe();
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
[main] INFO - onSubscribe()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler))
.log()
.subscribe();
[main] INFO - ?
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
[main] INFO - onSubscribe()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler))
.log()
.subscribe();
[main] INFO - ?
[pool-1] INFO - onNext(Something(id=1))
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
[main] INFO - onSubscribe()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler))
.log()
.subscribe();
[main] INFO - ?
[pool-1] INFO - onNext(Something(id=1))
[pool-2] INFO - onNext(Something(id=2))
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
[main] INFO - onSubscribe()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler))
.log()
.subscribe();
[main] INFO - ?
[pool-1] INFO - onNext(Something(id=1))
[pool-2] INFO - onNext(Something(id=2))
[pool-3] INFO - onNext(Something(id=3))
Concurrency
Flux.fromIterable(ids)
API
Service
some
thing
Scheduler scheduler =
Schedulers.newParallel("pool", 8);
[main] INFO - onSubscribe()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler))
.log()
.subscribe();
[main] INFO - ?
[pool-1] INFO - onNext(Something(id=1))
[pool-2] INFO - onNext(Something(id=2))
[pool-3] INFO - onNext(Something(id=3))
[pool-3] INFO - onComplete()
Concurrent
Remote Calls
Event-Driven
Systems
Distributed
Systems
Reactive
Systems
Reactive
Manifesto
Reactive
Streams
Reactive
Extensions
Marble
Diagrams
RxJava
Reactor
Akka Streams
Backpressure
Declarative
Programming
Higher-Order
Functions
Backpressure
Backpressure
Publisher
Backpressure
Publisher Subscriber
Backpressure
Publisher Subscriber
subscribe
Backpressure
Publisher Subscriber
Backpressure
Publisher Subscriber
1onNext( )
Backpressure
Publisher Subscriber
onNext( )2
Backpressure
Publisher Subscriber
onNext( )3
Backpressure
Publisher Subscriber
onNext( )4
Backpressure
Publisher Subscriber
onNext( )5
5
Backpressure
Publisher Subscriber
onNext( )
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
ids.stream()
.map(id -> executorService
.submit(() -> somethings.loadById(id)))
.collect(toList())
.stream()
.map(waitForSomething())
.forEach(handleSomething());
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
ids.stream()
.map(id -> executorService
.submit(() -> somethings.loadById(id)))
.collect(toList())
.stream()
.map(waitForSomething())
.forEach(handleSomething());
java.util.concurrent.RejectedExecutionException:
Task rejected from ThreadPoolExecutor [
Running, pool size = 2, active threads = 2,
queued tasks = 4, completed tasks = 0
]
Backpressure
Backpressure
Publisher
Backpressure
Publisher Subscriber
Backpressure
Publisher Subscriber
subscribe
Backpressure
Publisher Subscriber
Backpressure
Publisher Subscriber
request(3)
Backpressure
Publisher Subscriber
Backpressure
Publisher Subscriber
1onNext( )
Backpressure
Publisher Subscriber
onNext( )2
Backpressure
Publisher Subscriber
onNext( )3
Backpressure
Publisher Subscriber
Backpressure
Publisher Subscriber
request(2)
Backpressure
Publisher Subscriber
Backpressure
Publisher Subscriber
onNext( )4
Backpressure
Publisher Subscriber
onNext( )5
5
Backpressure
Publisher Subscriber
onNext( )
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | ?
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | ?
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | request(3)
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | request(3)
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | request(3)
[main] INFO - | onNext(1)
[main] INFO - | onNext(2)
[main] INFO - | onNext(3)
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | request(3)
[main] INFO - | onNext(1)
[main] INFO - | onNext(2)
[main] INFO - | onNext(3)
[pool-1] INFO - | request(1)
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | request(3)
[main] INFO - | onNext(1)
[main] INFO - | onNext(2)
[main] INFO - | onNext(3)
[pool-1] INFO - | request(1)
[pool-1] INFO - | onNext(4)
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | request(3)
[main] INFO - | onNext(1)
[main] INFO - | onNext(2)
[main] INFO - | onNext(3)
[pool-1] INFO - | request(1)
[pool-1] INFO - | onNext(4)
[pool-2] INFO - | request(1)
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | request(3)
[main] INFO - | onNext(1)
[main] INFO - | onNext(2)
[main] INFO - | onNext(3)
[pool-1] INFO - | request(1)
[pool-1] INFO - | onNext(4)
[pool-2] INFO - | request(1)
[pool-2] INFO - | onNext(5)
5
Backpressure
Publisher Subscriber
onNext( )
ExecutorService executorService =
new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(capacity)
);
Flux.fromIterable(ids)
.log()
.flatMap(id -> Mono.just(id)
.map(somethings::loadById)
.subscribeOn(scheduler), 3)
.doOnNext(handleSomething())
.subscribe();
Scheduler scheduler = Schedulers
.fromExecutorService(executorService);
[main] INFO - | onSubscribe()
[main] INFO - | request(3)
[main] INFO - | onNext(1)
[main] INFO - | onNext(2)
[main] INFO - | onNext(3)
[pool-1] INFO - | request(1)
[pool-1] INFO - | onNext(4)
[pool-2] INFO - | request(1)
[pool-2] INFO - | onNext(5)
[pool-2] INFO - | onComplete()
Backpressure
HTTP
Service A
Database
Topic Service B Topic
BFF
Backpressure
HTTP
Service A
Database
Topic Service B Topic
BFF
HTTP
Service A
Database
Topic Service B Topic
BFF
Progressive HTML Rendering
Progressive HTML RenderingProgressive
Progressive HTML Rendering
BFF
Widget Widget
Widget
Widget Widget
Concurrent Remote Calls
Progressive HTML Rendering
Transfer-Encoding: chunked
BFF
Widget Widget
Widget
Widget Widget
Concurrent Remote Calls
Progressive HTML Rendering
Progressive HTML Rendering
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Progressive HTML Rendering
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Progressive HTML Rendering
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Progressive HTML Rendering
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RestController
@GetMapping(value = "hello")
public Flux<String> sayHello() {
return Flux.interval(Duration.ofMillis(500)).map(tick -> "Hellon");
}
Progressive HTML Rendering
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
curl -i -N localhost:8080/hello
@RestController
@GetMapping(value = "hello")
public Flux<String> sayHello() {
return Flux.interval(Duration.ofMillis(500)).map(tick -> "Hellon");
}
Progressive HTML Rendering
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
curl -i -N localhost:8080/hello
@RestController
@GetMapping(value = "hello")
public Flux<String> sayHello() {
return Flux.interval(Duration.ofMillis(500)).map(tick -> "Hellon");
}
HTTP/1.1 200
Content-Type: text/plain
Transfer-Encoding: chunked
Progressive HTML Rendering
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
curl -i -N localhost:8080/hello
@RestController
@GetMapping(value = "hello")
public Flux<String> sayHello() {
return Flux.interval(Duration.ofMillis(500)).map(tick -> "Hellon");
}
HTTP/1.1 200
Content-Type: text/plain
Transfer-Encoding: chunked
Hello
Progressive HTML Rendering
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
curl -i -N localhost:8080/hello
@RestController
@GetMapping(value = "hello")
public Flux<String> sayHello() {
return Flux.interval(Duration.ofMillis(500)).map(tick -> "Hellon");
}
HTTP/1.1 200
Content-Type: text/plain
Transfer-Encoding: chunked
Hello
Hello
Progressive HTML Rendering
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
curl -i -N localhost:8080/hello
@RestController
@GetMapping(value = "hello")
public Flux<String> sayHello() {
return Flux.interval(Duration.ofMillis(500)).map(tick -> "Hellon");
}
HTTP/1.1 200
Content-Type: text/plain
Transfer-Encoding: chunked
Hello
Hello
Hello
Progressive HTML Rendering
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
curl -i -N localhost:8080/hello
@RestController
@GetMapping(value = "hello")
public Flux<String> sayHello() {
return Flux.interval(Duration.ofMillis(500)).map(tick -> "Hellon");
}
HTTP/1.1 200
Content-Type: text/plain
Transfer-Encoding: chunked
Hello
Hello
Hello
Hello
Baking an HTML stream
Spring 5 WebClient
Spring 5 WebClient
WidgetService
Spring 5 WebClient
WidgetService WebClient
Spring 5 WebClient
WidgetServerWidgetService WebClient
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
.get()
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
.get()
.uri("http://widget-server")
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
.get()
.uri("http://widget-server")
.retrieve()
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
.get()
.uri("http://widget-server")
.retrieve()
.bodyToFlux(String.class)
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
.get()
.uri("http://widget-server")
.retrieve()
.bodyToFlux(String.class)
.subscribe(this::onNext);
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
.get()
.uri("http://widget-server")
.retrieve()
.bodyToFlux(String.class)
.subscribe(this::onNext);
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
.get()
.uri("http://widget-server")
.retrieve()
.bodyToFlux(String.class)
.subscribe(this::onNext);
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
.get()
.uri("http://widget-server")
.retrieve()
.bodyToFlux(String.class)
.subscribe(this::onNext);
Spring 5 WebClient Network Boundary
WidgetServerWidgetService WebClient
WebClient.create()
.get()
.uri("http://widget-server")
.retrieve()
.bodyToFlux(String.class)
.subscribe(this::onNext);
HTML Rendering
HTML Rendering
HTTP Request
HTML Rendering
HTTP Request
Accepter Thread
HTML Rendering
HTTP Request
Accepter Thread Connection Queue
HTML Rendering
HTTP Request
Accepter Thread Connection Queue Request Thread
HTML Rendering
HTTP Request
Accepter Thread Connection Queue Request Thread
Model
HTML Rendering
HTTP Request
Accepter Thread Connection Queue Request Thread
Model
TemplateHttpResponse.getWriter()
Output Buffer
HTML Rendering
HTTP Request
Accepter Thread
HTTP Response
Connection Queue Request Thread
Model
TemplateHttpResponse.getWriter()
Output Buffer
Model
Reactive-Friendly Template Engine
Template
Model
Reactive-Friendly Template Engine
Flux<T>
Template
th:each="..."
Model
Reactive-Friendly Template Engine
Flux<T>
Template
th:each="..."
Model
Reactive-Friendly Template Engine
Flux<T>
Template
th:each="..."
Reactive
Web Server
Model
Reactive-Friendly Template Engine
Flux<T>
Template
th:each="..."
Reactive
Web Server
subscribes
Model
Reactive-Friendly Template Engine
Flux<T>
Template
th:each="..."
requests
DataBuffer
Reactive
Web Server
subscribes
Processor<T, DataBuffer>
Model
Reactive-Friendly Template Engine
Flux<T>
Template
th:each="..."
requests
DataBuffer
Reactive
Web Server
subscribes
Processor<T, DataBuffer>
Model
Reactive-Friendly Template Engine
Flux<T>
Template
th:each="..."
requests
DataBuffer
Reactive
Web Server
subscribes
HTML rendered as values are published
Progressive HTML Rendering
Progressive HTML Rendering
Processor
Subscriber
Publisher
Progressive HTML Rendering
Processor
Subscriber
Publisher Spring 5 WebClient
Progressive HTML Rendering
Processor
Subscriber
Publisher Spring 5 WebClient
Reactive-Friendly Thymeleaf View
Progressive HTML Rendering
Processor
Subscriber
Publisher Spring 5 WebClient
Reactive-Friendly Thymeleaf View
Reactive Web Server
Progressive HTML Rendering
Publisher Spring 5 WebClient
URI uri = ...
String name = ...
Mono<Pagelet> pageletMono = webClient.get().uri(uri).exchange()
.flatMap(res -> res.bodyToMono(String.class))
.map(body -> new Pagelet(name, body));
List<Mono<Pagelet>> pageletMonos = ...;
... = Flux.merge(pageletMonos);
Progressive HTML Rendering
Publisher Spring 5 WebClient
Progressive HTML Rendering
Processor Reactive Thymeleaf View
@GetMapping(value = "/widgets")
public String widgets(Model model) {
List<String> pageletNames = widgetService.getPageletNames();
Flux<Pagelet> pagelets = widgetService.loadPagelets();
model.addAttribute("pageletNames", pageletNames);
model.addAttribute("pagelets", new ReactiveDataDriverContextVariable(pagelets, 1));
return "widgets";
}
Progressive HTML Rendering
Processor Reactive Thymeleaf View
@GetMapping(value = "/widgets")
public String widgets(Model model) {
List<String> pageletNames = widgetService.getPageletNames();
Flux<Pagelet> pagelets = widgetService.loadPagelets();
model.addAttribute("pageletNames", pageletNames);
model.addAttribute("pagelets", new ReactiveDataDriverContextVariable(pagelets, 1));
return "widgets";
}
Progressive HTML Rendering
Processor Reactive Thymeleaf View
ReactiveDataDriverContextVariable
<article th:each="pagelet: ${pagelets}" class="...">
<section class="...">
<div class="...">
<div th:utext="${pagelet.content}" class="..."></div>
</div>
</section>
</article>
Progressive HTML Rendering
Processor Reactive Thymeleaf View
<article th:each="pagelet: ${pagelets}" class="...">
<section class="...">
<div class="...">
<div th:utext="${pagelet.content}" class="..."></div>
</div>
</section>
</article>
Progressive HTML Rendering
Processor Reactive Thymeleaf View
List<Mono<Pagelet>> pageletMonos = ...;
... = Flux.merge(pageletMonos);
<article th:each="pagelet: ${pagelets}" class="...">
<section class="...">
<div class="...">
<div th:utext="${pagelet.content}" class="..."></div>
</div>
</section>
</article>
Progressive HTML Rendering
Processor Reactive Thymeleaf View
List<Mono<Pagelet>> pageletMonos = ...;
... = Flux.merge(pageletMonos);Flux.merge(pageletMonos)
<article th:each="pageletName: ${pageletNames}" class="...">
<section class="...">
<div th:id="${pageletName}" class="...">
<div class="...">...</div>
</div>
</section>
</article>
Progressive HTML Rendering
Processor Reactive Thymeleaf View
<script th-inline="javascript" th:each="pagelet : ${pagelets}">
(function() {
var element = document.getElementById("[[${pagelet.name}]]");
if(element) {
element.innerHTML = "[(${pagelet.content})]";
}
})();
</script>
Progressive HTML Rendering
Processor Reactive Thymeleaf View
Does it work?
Progressive HTML Rendering
Progressive HTML Rendering
Progressive HTML Rendering
Progressive HTML Rendering
Progressive HTML Rendering
What‘s more?
§ Cold Streams vs. Hot Streams
§ Reactive Database Drivers
§ Backpressure Strategies
§ Threaded vs. Evented Server Model
§ WebSockets and Server-Sent Events
Thank you!
Questions?

Reactive Streams in the Web