© Copyright 2017 Pivotal Software, Inc. All rights Reserved. Version 1.0
Stéphane MALDINI
Simon BASLÉ
Project Reactor
Now & Tomorrow
SpringOne Platform 2017
Stéphane Maldini
Senior Product Manager
& Lead Engineer
Project Reactor, Pivotal
&
@smaldini
Simon Baslé
@simonbasle
Staff Software Engineer
Project Reactor, Pivotal
Now
3.0
→
3.1
Is this a big deal ?
Spoiler: yes
Reactor 3.0
Versioning scheme: ERA.MAJOR.MINOR
Production ready, but had to be out before Spring Framework 5
Still room for evolution after plenty of feedback
Project ____
Downloads :
> 300k/month
projectreactor.io :
> 30k unique/month
Gitter.im : #7 Java channel
Project ____
452 Closed Pull Requests
487 Closed Issues
11 Releases
53 Contributors
4 (+2) Core Committers
Shoutout to our Contributors
Why users want Reactor ?
Why users want Reactor ?
Reactive Streams
sample extract
reactive-streams.org
Why users want Reactor ?
Flux.range(from, count)
Do you speak
Reactive Streams ?
Reactor and Reactive Streams
Publisher<T>
&
Subscriber<T>
Publisher<Music>
Subscriber<Music>
Publisher<T>
void subscribe(Subscriber<T>)
Subscriber<T>
void onNext(T)
void onComplete()
void onError(Throwable)
Subscription [Music]
Subscription
void request(long)
void cancel()
Subscriber<T>
void onSubscribe(Subscription)
void onNext(T)
void onComplete()
void onError(Throwable)
This is
Flow Control
Flux
0-N elements
“classic” Publisher
Mono
0-1 elements
“specialized” Publisher
String res;
for(int i = 1; i <= 1000; i++) {
res = blockingHttpGet(“/quote/”+i);
handleBody(res);
}
Without --------
String res;
for(int i = 1; i <= 1000; i++) {
res = blockingHttpGet(“/quote/”+i);
handleBody(res);
}
1000 calls later...
Main Thread
Without --------
Flux.range(1, 1000)
.flatMap(i -> reactiveHttpCall("/quote/"+i))
.subscribe(this::handleBody);
With --------
Flux.range(1, 1000)
.flatMap(i -> reactiveHttpCall("/quote/"+i))
.subscribe(this::handleBody);
operator
With --------
Flux.range(1, 1000)
.flatMap(i -> reactiveHttpCall("/quote/"+i))
.subscribe(this::handleBody);
1 call later
Main Thread
With --------
Flux.range(1, 1000)
.flatMap(i -> reactiveHttpCall("/quote/"+i))
.subscribe(this::handleBody);
256 http calls
With --------
Flux.range(1, 1000)
.flatMap(i -> reactiveHttpCall("/quote/"+i), 1)
.subscribe(this::handleBody);
With --------
1 http call
Flux.range(1, 1000)
.flatMap(i -> reactiveHttpCall("/quote/"+i), 1)
.subscribe(this::handleBody);
With --------
J-j-jeez Rick, I like
this backpressure
stuff
1 http call
At every stage,
Flux and Mono are
Publisher
interface SomeReactiveApi {
Flux<T> findUsersLike(Publisher<String> usernames)
}
//...
someReactiveApi.findUsersLike(Mono.just(“ricksanchez”))
.subscribe(handleUsers);
Everywhere
interface SomeReactiveApi {
Flux<T> findUsersLike(Publisher<String> usernames)
}
//...
someReactiveApi.findUsersLike(Mono.just(“ricksanchez”))
.subscribe(handleUsers);
//or
someReactiveApi.findUsersLike(someFlux.filter(“sanchez”::endsWith))
.subscribe(handleUsers);
Everywhere
interface SomeReactiveApi {
Flux<T> findUsersLike(Publisher<String> usernames)
}
//...
someReactiveApi.findUsersLike(Mono.just(“ricksanchez”))
.subscribe(handleUsers);
//or
someReactiveApi.findUsersLike(someFlux.filter(“sanchez”::endsWith))
.subscribe(handleUsers);
Everywhere
Look Morty, the
universe wants API
consistency !
Reactor Core
3.1
Hot from the oven
3.0
→
LTS 3.1
3.1 is a polished
programming
experience
Flux Mono
Aligning APIs between Flux and Mono
Flux Mono
flatmap then
Flux Mono
flatmap then
Flux Mono
flatmap flatmap
Practicalstatic
extensions
Initial N
ullability
support
“Reactor has matured,
laying out the foundations
for frameworks and users
to integrate their context
into the Core .”
Enterprise Ready:
Hooks & Customization,
Matured API
Cross-Cutting Concerns
Scannable
Visit the hierarchy of operators
Scan at each step for Attributes...
… either upstream (parents()) or downstream
(actuals())
Tagging and Naming
give a chain of
operator a name
give a chain of
operator attributes
Tagging and Naming
Flux.range(1, 10)
.name("myRange")
.tag("createdBy", "rick");
Hooks
On last operator
instantiation before
subscribe
On every operator
instantiation
Hooks.onLastOperator(
Operators.lift(scannable ->
scannable.tags()
.anyMatch(t -> t.getT1()
.contains("createdBy")),
(scannable, subscriber) -> {
fooService.registerOwnedFlux(scannable);
return subscriber;
}));
Hooks
Hooks.onLastOperator(
Operators.lift(scannable ->
scannable.tags()
.anyMatch(t -> t.getT1()
.contains("createdBy")),
(scannable, subscriber) -> {
fooService.registerOwnedFlux(scannable);
return subscriber;
}));
Hooks
Once every stream... Only visit streams containing tag...
Notify some service with the stream reference
and return the operator subscriber
Possible Application: Metrics
E.g. tag a Flux with “METRICS” and a framework
could pick that up and add an operator that
increments counters/timers...
Improved Testing Support
reactor-test
StepVerifier to assert expectations on any
Publisher
TestPublisher to simulate a reactive source
PublisherProbe to check which path was
used in complex chains
StepVerifier
StepVerifier.withVirtualTime(
() -> Flux.interval(Duration.ofSeconds(45)).take(2))
.thenAwait(Duration.ofSeconds(50))
.expectNext(0L)
.thenAwait(Duration.ofSeconds(40))
.assertNext(v -> assertThat(v).isGreaterThan(0L))
.expectComplete()
.verify();
TestPublisher
TestPublisher.create()
.next(1, 2, 3, 4)
.complete();
TestPublisher.createNonCompliant(Violation.ALLOW_NULLS)
.emit(1, 2, 3, null, 4);
next() +
complete()
PublisherProbe probe =
PublisherProbe.of(Flux.range(1, 10));
reactiveConditionalPath(Mono.just(1), probe.flux())
.subscribe();
probe.assertWasSubscribed();
PublisherProbe
The Context
Immutable
Context a = Context.of(“key”, “value”);
Context b = a.put(“key”, “value2”);
a.get(“key”); // ⇒ “value”
b.get(“key”); // ⇒ “value2”
Propagates during Subscription
[a=2,b=2]
[a=1,b=2]
[]
ctx.put(b,2)
[a=1]
ctx.put(a,1)
)subscribe( Subscriber
Subscribe
& Context
Data
ctx.put(a,2)
See Reference Guide for Snippets like this:
String key = "message";
Mono<String> r = Mono.just("Hello")
.subscriberContext(ctx -> ctx.put(key,"World"))
.flatMap( s -> Mono
.subscriberContext()
.map(ctx -> s + " " +
ctx.getOrDefault(key, "Stranger"))
);
StepVerifier.create(r)
.expectNext("Hello Stranger")
.verifyComplete();
More about Bismuth Release
https://spring.io/blog/2017/09/28/reactor-bismuth-is-out
Executive summary of latest 3.1 additions
What’s new in The Ecosystem ?
In-house or community grown initiatives
reactor-netty
Reactor Netty only turns Netty into a
Reactive Streams ready bridge
Just a few threads for
a production-ready
volume of connections
with various latencies
writeread
select
worker worker worker
Non Blocking TCP, UDP and HTTP
Decouples Read and Write
Pauses reading on local Backpressure
Pauses local producing on Backpressure
Webflux
WebClient
Cloud Gateway
Boot 2.0
Used and Distributed
by Spring
Vendor warning
Reactor Netty 0.7 API
Reactor Netty 0.7 API
Seriously, it’s 0.7.2.
It runs well.
But we’re polishing API before 0.8.0.M1
Best to use--------
reactor-kafka
Publish & Consume messages from Kafka...
… with a backpressure-ready Reactive API
https://github.com/reactor/reactor-kafka
Reactor-Kafka
Reactor-Kafka
KafkaSender.create()
.send(Flux.range(1, 10)
.map(i ->
SenderRecord.create(topic, partition,
timestamp, i, "msg_" + i,i)))
.subscribe();
reactor-rabbitmq
Reactor-RabbitMQ
Publish & Consume messages from RabbitMQ...
… with a backpressure-ready Reactive API
https://github.com/reactor/reactor-rabbitmq
Reactor-RabbitMQ
Flux<Delivery> inboundFlux =
ReactorRabbitMq.receiver(receiverOptions)
.consumeNoAck("reactive.queue")
.subscribe(messageHandler);
reactor-addons
Reactor-Addons
Extend Reactor Core API
Adapt Reactor to different execution contracts
https://github.com/reactor/reactor-addons
Retry Support
Factory methods that provide a fluent API to incrementally configure a Function
suitable for Flux.retryWhen.
(tip: use static import)
Retry.allBut(Class<Exception>...)
Retry.any()
Retry.anyOf(Class<Exception>...)
Retry.onlyIf(Predicate)
Retry Support
flux.retryWhen(allBut(BusinessException.class)
.retryMax(3)
.exponentialBackoff(Duration.ofMillis(100),
Duration.ofMillis(500))
.timeout(Duration.ofMillis(1500))
.jitter(Jitter.random())
.doOnRetry(this::retryCallback)
);
Math
Take a Flux<Number> and apply operations to its whole set
of elements using MathFlux static methods.
Mono<Long> sum = Flux.range(1, 10)
.as(MathFlux::sumLong);
Math (with kotlin)
Mono<Long> sum = Flux.range(1, 10)
.sumLong();
An opinionated abstraction that helps you store and lookup a
Mono or a Flux to/from a cache.
CacheMono will store the Mono value or completion as a
Signal.
CacheFlux will collect the elements and terminal event as a
List<Signal>.
Both only work with finite sources.
Cache Support
Mono<Integer> test =
CacheMono.lookup(reader(data), "foo")
.onCacheMissResume(() -> Mono.just(1L))
.andWriteWith(writer(data));
Reader and writer interfaces to adapt to any type of cache
implementation + out-of-the-box variant for Map.
Cache Support
Community Highlights
Spring Ecosystem
#1 user and source of feedback
Reactive talk with
reactive-gRPC
https://github.com/salesforce/reactive-grpc
Integrates reactive programming with grpc-java
From --
Lettuce
http://lettuce.io
A Reactor-based reactive driver for Redis
Tech.io
https://tech.io/playgrounds/929/reactive-programming-with-reactor-3
A community-driven programming learning platform
CloudFoundry Java Client
https://github.com/cloudfoundry/cf-java-client
The official Java client for CloudFoundry API
https://github.com/reactor/reactor-scala-extensions
Idiomatic Scala for Reactor
Reactor
Tomorrow
Near-and-far Future<T>
Near-and-far Future<T>
Reactor
Tomorrow
Reactor
Tomorrow
Near-and-far future
2019
2020
2017
2018
the Road from Here
2017
First,
a patch release
3.1.33.1.0
September
October
November
December
3.1.1 3.1.2
3.1.33.1.0
September
October
November
December
3.1.1 3.1.2
limitRequest,
Kotlin extensions...
3.1.33.1.0
September
October
November
December
3.1.1 3.1.2
index,
blockOptional...
3.1.33.1.0
September
October
November
December
3.1.1 3.1.2
Mostly bugfixes...
2018Themes for
Continue On Error
errors are terminal
continue on
(transient) errors?
NowFlux.range(0, 3)
.flatMap(v -> Mono.just(v)
.map(i -> 100 / i)
.doOnError(...)
.onErrorResume(
Mono.empty()
)
);
processing that
can fail
your face when
you read this code
TomorrowFlux.range(0, 3)
.map(i -> 100 / i)
.errorStrategyContinue()
.filter(i -> 100 / i > 4);
this is not impacted
and can still fail
this is protected
by this
Observability & Traceability
Micrometer
“SLF4J for metrics”
Sleuth & Zipkin
Currently focusing on tracing WebFlux and WebClient
any guess as to why these are different?
flatMap
concatMap
reactor-netty
API iteration release : 0.8.x
Immutable server and client builders
Lifecycle and interceptor API (doOnRequest...)
Initial user guide
API iteration release : 0.8.x
HttpClient.prepare()
.port(c.address().getPort())
.wiretap()
.get()
.uri("/test/test.css")
.responseContent()
.asString()
.reduce(String::concat)
.subscribe(System.out::println);
Beyond 0.8
HTTP 2 support
Plug-And-Play Metrics
Connection Pool improvements
Backpressure strategies
File I/O Addon?
Java 9
continuously improve support
Java 9
continuously improve support
RSocket
Exploring RSocket (rsocket.io)
Duplex flow-control support
Multi-transport and multi-language
Decoupled from any serialization
Contributors from Facebook
Beyondand
Reactor Kore?
Reactor Console?
ADBA ?
Just Wondering
Flux Buffers Lifecycle Hooks ?
Persistent Flux ?
Coordinated demand ?
What do you need ?
The end
Thank You!
Questions?

Project Reactor Now and Tomorrow