This is part 2 of a series on reactive microservices, the first part is available at https://marlo.com.au/reactive-microservices
In the last post we talked at length about why reactive applications are really useful and how they can help squeeze more performance out of your java applications.
Tl;dr: Backpressure and non blocking IO are amazing.
But the last post was a bit wordy, wasn't it? All exposition and no programming or anything.
"Get to the point Mills!" I can hear the readers shout into the void, as they echo various teachers, coaches, managers, relatives, my wife and so on throughout my life.
I hear you, however before I confront the reader with a wall of code, it's worth looking at some crucial differences between normal imperative coding and the reactive APIs.
In this series we are focusing on Spring Boot and its use of the Project Reactor reactive engine, however it's not the only game in town. Other production ready reactive java libraries include Akka and RxJava, and the JDK even gets in on the game with the Java 9 Flow API .
Mono and Flux
The first big difference in the way you write reactive application is that whatever happens, it happens reactively! That is to say, you have to kick off a reaction before it happens and it's quite a different approach when compared to something like just declaring and using a string.
So how do we declare and print some text?
In "normal" java I declare a
String and then use the
String theString = "Hey there, World!" System.out.println(theString);
This entails no waiting or reacting to anything. The computer put some bytes that form the string in memory and returned a reference, which I can use to conquer the world (or print to the console, either/or).
Reactive does not do that, at least not right away. Instead you ask it to acquire some data from somewhere and then instruct it on what to do when the data becomes available.
Project Reactor provides two high level APIs to do things reactively, the
Mono and the
Flux, which are the entry points to the reactive engine.
It's pretty simple to start:
Mono<String> stringMono = Mono.just("Hey there, World");
Ok, so you just created another String? Great.
But did I? Reactive is all about reacting to events that are outside of the direct control of the program. Central to this is setting up the event (or reaction) and then submitting it to the reactive core to wait for the thing to happen.
So what are the differences? The computer still put some bytes that form a string in memory and returned a reference (because I'm statically declaring a string), but the
Mono#just does not return a
String, it returns the
The reactor core is sitting on the
String until something else happens.
That something is a concept of subscription. This is where it starts to get a little hairy.
Back to our
String. We had a simple
Mono that is of type
Mono<String> stringMono = Mono.just("Hey there, World");
To actually do something to the
String, I need to subscribe and supply something to consume the result:
Mono<String> stringMono = Mono.just("Hey there, World"); stringMono.subscribe(System.out::println);
Mono#just method set up the reactor to provide the string, but it did not actually occur until
- we subscribe to the sequence with a
In this case the
String was available to the
System.out.println() method right away but, in real life, we may have to wait. The reactor core is responsible for managing that waiting in the most efficient way possible, which it does by putting the pending job to the side and using the threads for more useful work.
Something that was not overly obvious initially, (at least to me, leading to furrowed brows and confusion), is this concept of the reaction happening inside the reactive container.
When reactive-ing, trying to do this is probably not what you want:
Mono<String> stringMono = Mono.just("Hey there, World"); String theString = stringMono.subscribe() <-- compilation error
This is the first thing I tried when learning the reactive API. It does not compile because the
subscribe method returns a
Disposable, which is of no use to someone trying to get a
String. Of course, the next thing I tried was this:
String theString = stringMono.subscribe().toString(); // lol
even though I knew it was not right, I did it anyway to prove a point. It was not right and I was greeted with an object reference.
The point that I did not know what I was doing was proven.
There was a way to force it to give me a string. This:
String theString = stringMono.block()
will compile, but this only works because it blocks the thread until the
String is available, and that is not why we are here.
"How do I get my String?!?!?!?!", the developer will cry in frustration. This question comes up all the time.
How do I get the
Monoto cough up my long-lost data?
Well, you don't.
The paradigm shift is that you need to start thinking inside the (reactive) box. Remember subscription is submitting your process to the reactive core, which is then going to work out the nuts and bolts of when things happen. Whatever you want to do with the String happens inside (or via) the
For me, the lesson was do not think of the reactive container as something that is going to provide your program with an answer, you need to give it whatever you want to do with the answer as well, like this:
Mono<String> stringMono = Mono.just("Hey there, World"); stringMono.subscribe(s -> doTheThingWithTheString(s));
Much like an actual nuclear reactor, if the stuff you put in comes out, you are doing it wrong. Really, you do not want it to come out without knowing exactly what you are doing.
Got it. Do the things inside the Mono. So then what is the Flux all about?
The same trivial example with a
Flux looks like this:
Flux<String> stringFlux = Flux.just("Hey", "there,", "World"); stringFlux.subscribe(System.out::println);
The difference being that it is working on a collection of
Strings rather than a single
This will apply the subscribe method action to each item on the stream:
Hey There World
Mono is a single (a bounded sequence of 0 .. 1), whereas a
Flux is a unbounded sequence (0 .. n). Under the covers they implement the same
Publisher interface, but there is some aspects that make it useful to provide separate APIs.
Great! Is that it then?
Unfortunately for you, dear reader, no. In fact, for a post entitled "Reactive Microservices" there isn't much in the way of microservices, unless you consider printing to the console a microservice.
Therefore, we must carry on! I reckon the best way to appreciate "how to reactive" is to put it side by side with a non-reactive app, so in part 3 (I can hear the groans already) I will get busy developing the new "Customer Microservice" for our new super-hot client that will be soon taking over the world - CompuHyperGlobalMegaNet.
Until next time, thanks for reading!