Project reactor
from here to the moon and back again
Good news everyone
- a bit of functional
- reactive streams intro
- where it came from
- what are the building blocks
- why it's so special
- implementations
- project reactor
- 0..1 and 0..n signals
- lets' produce something!
- Smooth
criminaloperator
- buckle up the fun just starting
- am I cold or it's just hot inside
- one tread, two threads, do I hear a pool?
- so you said you work in legacy and wanna be reactive
- and more useful stuff
Just for a wrap up.
Reactive streams
Lets describe the problem we've got
- Live system with unpredictable amount of data
- Multiple API components that communicate
- Lack of resources in the system
- Going async for faster execution
And what solution we've got?
We could buy more servers,
we could wait a minute longer
and in the end we end up with.
but maybe we could do something else?
Reactive Manifesto
Let's imagine that we've got some kind of infinite queue
but on the left and right ends we've got producer and consumer that tries to communicate
How can they do it?
Let's try some sketching.....
what if we combine both pull and push model?
Reactive stream API defines
- Publisher
- Subscriber
- Subscription
- Processor
Publisher
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
Subscriber
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
Subscription
public interface Subscription {
public void request(long n);
public void cancel();
}
Processor
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
Example implementations:
- Akka streams
- Project Reactor
- jdk9 Flow
- rxJava
- Vert.x
Project reactor
have two main elements
Mono<T>
Flux<T>
Flux
Mono
Let us publish something
// Flux
Flux<String> f = Flux.just("a", "b", "c");
Flux<Integer> g = Flox.fromIterable(Arrays.asList(1, 2, 3));
//Mono
Mono<String> m = Mono.empty();
Mono<Integer> n = Mono.just(1);
and then consume it...
Publisher<T> publisher = ...;
publisher.subscribe(t -> {...});
but what about
let us go through some of them
- map
- filter
- interval
- merge
- zip
MAP
public final <V> Flux<V> map(Function<? super T,? extends V> mapper)
Flux<String> foo = Flux.just("1", "2", "3");
foo.map(Integer::valueOf)
.map(i -> i = i * 2)
.subscribe(i -> log.info("Mapped value: {}", i));
Filter
public final Flux<T> filter(Predicate<? super T> p)
Flux<Integer> foo = Flux.just(1, 2, 3, 4, 5, 6);
foo.filter(i -> i % 2 == 0)
.subscribe(i -> log.info("Even numbers: {}", i));
Interval
public static Flux<Long> interval(Duration period)
Flux<Integer> foo = Flux.interval(Duration.ofSeconds(2));
foo.subscribe(i -> log.info("Emitting every 2 seconds: {}", i));
Merge
public static <I> Flux<I> merge(Iterable<? extends Publisher<? extends I>> sources)
Flux<Integer> foo = Flux.range(1,10);
Flux<Integer> bar = Flux.range(2,10);
Flux<Integer> mergedFlux = Flux.merge(Arrays.asList(foo, bar));
mergedFlux.subscribe(i -> log.info("Merged: {}", i));
Zip
public static <TUPLE extends Tuple2,V> Flux<V> zip(Publisher<? extends Publisher<?>> sources,
Function<? super TUPLE,? extends V> combinator)
Flux<String> nameFlux = Flux.just("Krzysiek","Zenek","Witek");
Flux<String> lastNameFlux = Flux.just("Kowalski","Nowak","Brzeczyszczykiwicz");
Flux<String> positionFlux = Flux.just("CEO","CTO","COO");
Flux<Worker> zippedFlux = Flux.zip(nameFlux, lastNameFlux, positionFlux)
.flatMap(zipped ->
Flux.just(new Worker(
zipped.getT1(),
zipped.getT2(),
zipped.getT2()
)
)
);
zippedFlux.subscribe(worker -> log.info("Zipped worker: {}", worker));
and much, much more....
buckle up the fun just starting
do you know that the stream might be hot or cold?
It's like the difference between TV and Netflix.
- Hot publishers emits events starting from the creation. When subscribing hot publisher subscriber will continue receiving events.
- Cold publishers emits events after the subscription. Each new subscription starts new stream of data.
private fun coldFlux(): Flux<String> {
return Flux.interval(Duration.ofSeconds(2)).map { i -> "Emitting $i" }
}
private fun hotFlux(): Flux<String> {
return coldFlux().publish().autoConnect();
}
@Test
fun run() {
val flux = hotFlux().take(10)
flux.subscribe { i -> println("First subscriber: $i") }
Thread.sleep(5000);
flux.subscribe { i -> println("Second subscriber: $i") }
flux.subscribe { i -> println("Third subscriber: $i") }
flux.subscribe { i -> println("Fourth subscriber: $i") }
flux.subscribe { i -> println("Fifth subscriber: $i") }
Thread.sleep(20000);
}
The execution context
By default project reactor is thread agnostic. User decides how those task should be handled
But there are some exceptions:
- delay, interval by default use parallel thread pool
And it can be changed by using operators
By default project reactor is thread agnostic. User decides how those task should be handled
But there are some exceptions:
- delay, interval by default use parallel thread pool
And it can be changed by using operators
publishOn
subscribeOn
that's all folks!
Project reactor - from here to the moon and back again
By Krzysztof Folwarczny
Project reactor - from here to the moon and back again
- 158