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 criminal operator
  • 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