Reactive Programming has long since begun its journey from pure web applications to Java Enterprise applications. It promises higher performance and lower memory footprints through the use of asynchronous non-blocking calls.
Most likely, you already read some documentation and blog posts about Reactive Programming or one of its implementations in Java. In this series, it is not my intention to go into the deep details of how to program using one of the existing Reactive frameworks. I will rather elucidate what it actually means to use reactive programming and what obstacles are going to await you. Common things like debugging, testing, and exception handling require new or different approaches, as asynchronicity comes with a price. This topic is often concealed and only summarized with “The learning curve from imperative to reactive is steep”. That’s why I dedicate this series to these topics.
One side note: Examples will be written in Kotlin. Don’t be scared if you’re not familiar with kotlin. The examples are easy to comprehend and look pretty similar in different languages. For a first insight into the language, you can also take a look at my colleague’s blog post: 7 things any Java developer should know when starting with Kotlin
As an absolute Spring Framework enthusiast, I use the Spring Reactor framework which is extensively implementing the Reactive Streams specification and the reactive-stack web framework Spring Webflux.
Let’s just build everything reactive
Sometimes, in discussions it feels like as if Reactive Programming is the new saviour out of the programming heaven that will solve all problems. I want to make one thing clear from the beginning: Reactive Programming is not the new silver bullet. I wouldn’t recommend it for the bog standard Java backend application and the everyday project. Reactive Programming starts to shine when your application has a requirement to serve ten-thousands of concurrent users and you simply cannot handle each incoming request in its own web servlet thread since threads in Java are not cheap.
Most definitions about Reactive Programming describe that it handles data streams and is concerned with propagation of changes. Since that never meant anything useful to me, I researched and stumbled across the following description on Codingame that was way easier to get my head around:
“Reactive Programming is a new paradigm in which you use declarative code (in a manner that is similar to functional programming) in order to build asynchronous processing pipelines. It is an event-based model where data is pushed to the consumer, as it becomes available: we deal with asynchronous sequences of events.”
Reactive code is assembled around asynchronous functional chains, where inputs are streamed (propagated) through these chains from the producer to the subscribers.
So, the most important question is: What problem does Reactive Programming actually solve?
The disillusioning answer is, everything you can solve with Reactive Programming, is possible to solve using other approaches, too. There is no real “If you have problem X, just go reactive and everything will be fine”. Even if you are working with data streams and sequences of events, Reactive Programming is not necessarily the best fit. For the inexperienced reader, the code will look even more unreadable because of the level of abstraction that reactive code delivers.
But for the same reason, the code will most likely be more resilient and extensible. Issues like backpressure, retry logic and the overall handling of asynchronous non-blocking calls is abstracted away from you. Retrying a service call is as easy as adding a
retry() to the execution chain.
Bear in mind the difference between the antiquated for loop with explicit counter and the foreach abstraction:
Differents variants of the foreach abstration:
Ultimately all do the same, but the logic for the iteration and the assignments are abstracted away by the foreach. Even if you actually need the element index, which happens very rarely, you can simply use
forEachIndexed. The next list element with its index will then be provided in each iteration. The biggest advantage, however, is not obvious. These abstractions have been implemented and tested and are used by thousands of developers without any concerns. And the same applies to the abstractions of reactive libraries for handling data streams. Frameworks like RxJava and Spring Reactor are proven to work, extensively tested, and offer such a high level of abstraction. So, if you are working with streams of data and asynchronous sequences of events, then think about using some battle-proof reactive implementation.
The Core: Reactive Streams
The core element of reactive streams is the Publisher-Subscriber pattern, contrary to the familiar Iterable-Iterator pattern, which is pull-based.
Publishers produce values and notify the Subscriber of newly available values. Compared to the Iterator-Iterable program flow, this is exactly the other way around. Values are pushed from the publisher to the subscribers, and not pulled by the subscriber.
“A Publisher is a provider of a potentially unbounded number of sequenced elements, publishing them according to the demand received from its Subscribers.
A Publisher can serve multiple Subscribers subscribed dynamically at various points in time.”
Publishers can have different cardinalities, based on what they are actually representing. The following examples use Spring Reactor, a implementation of the Reactive Streams specification. Reactor provides composable reactive types that implement the Publisher interface, depending on the desired cardinality: Mono and Flux.
While Flux describes data streams with a cardinality of 0..n elements, Mono represents results with 0..1 elements. With a few exceptions, all operators work on both reactive types.
The following example shows a Flux in the shape of a Twitter stream in action:
Similar to functional programming, the program logic is expressed declaratively. We describe the result of the execution (we want a filtered and transformed list of tweets) and not the way to get to the result.
In additional to handling each pushed value, the subscriber may also react on error signals or the completion signal:
Flux and Mono may be converted into each other depending on the operation. If a Mono generates multiple results, the result type is converted into a Flux:
A Mono is created from scratch by calling
Mono.just. The Mono is then converted into a Flux by applying prime factorization on the wrapped value. The prime factors are then gathered and combined into a string, generating a Mono again. Finally, subscribe is called on the Mono object and the result string is printed.
This concludes the first section of this blog post series about Reactive Programming. In this part I introduced you to Reactive Programming and discussed when to use Reactive Programming and when not. Using some code examples I have shown you the general workflow of reactive pipelines and the core elements of the Spring Reactor framework.
In the next parts of this series I will show you the expected difficulties in handling and writing reactive code and what alternative patterns are essential for everyday development.
Thanks for reading! Feel free to comment or message me, when you have questions or suggestions.