The document provides an overview of reactive programming with Spring Reactor. It defines key concepts like reactive programming, asynchronous and non-blocking approaches, and reactive streams. It then discusses how Spring Reactor allows achieving reactive approaches in Java applications through components like Flux and Mono that represent push-based streams. Code examples are provided to demonstrate defining publishers and subscribers as well as building a simple reactive server with Spring.
18. CAUTION
BUZZ WORDS
AHEAD
• A specification for building modern, cloud-scale architectures
• which are responsive, resilient, elastic and message driven.
Reactive manifesto
• A system that fulfill the reactive manifesto.
Reactive system
• A specification much like JPA or JDBC, product of a collaboration
• between engineers from Netflix, Pivotal, Red Hat, Twitter,
• Typesafe, and many others.
Reactive Streams
• A specification for denotative continuous-time programming.
• Most libraries claiming to support functional reactive
• programming are almost exclusively talking about reactive
• programming.
Functional reactive programming
• An asynchronous programming paradigm concerned with data
• streams and the propagation of change.
Reactive programming
19. Thinking in Reactive
The hardest part of this journey
is thinking in Reactive.
It's a lot about letting go of old
imperative and stateful habits of
typical programming and forcing
your brain to work in a different
paradigm.
20. Do I have to make
it all reactive?
Only consider reactive programming when dealing
with huge volumes of data or multi-userness.
36. Server-Sent
Events
An HTTP standard that allows a web application
to handle a unidirectional event stream and
receive updates whenever server emits data.
42. Why does
it take so long?
RestTemplate uses the Java Servlet API, which is based
on the thread-per-request model [sync/blocking].
WebClient uses an asynchronous, non-blocking solution
provided by the Spring Reactive framework.
WebClient
rocks!
From the application's perspective, the read call spans a long duration. But, in fact, the application is actually blocked while the read is multiplexed with other work in the kernel.
The implication of non-blocking is that an I/O command may not be satisfied immediately, requiring that the application make numerous calls to await completion. This can be extremely inefficient because in many cases the application must busy-wait until the data is available or attempt to do other work while the command is performed in the kernel. As also shown in Figure, this method can introduce latency in the I/O because any gap between the data becoming available in the kernel and the user calling read to return it can reduce the overall data throughput.
In this model, non-blocking I/O is configured, and then the blocking select system call is used to determine when there's any activity for an I/O descriptor. The primary issue with the select call is that it's not very efficient. While it's a convenient model for asynchronous notification, its use for high-performance I/O is not advised.
The read request returns immediately, indicating that the read was successfully initiated. The application can then perform other processing while the background read operation completes. When the read response arrives, a signal or a thread-based callback can be generated to complete the I/O transaction.
The ability to overlap computation and I/O processing in a single process for potentially multiple I/O requests exploits the gap between processing speed and I/O speed. While one or more slow I/O requests are pending, the CPU can perform other tasks or, more commonly, operate on already completed I/Os while other I/Os are initiated.
Let’s focus on two keywords here.
Here’s a real-life example. Say, it’s Friday and Alice wants to spend this evening with his friend Bob, scarfing pizza and watching one of the Star Wars episodes. Let’s outline the options she has.
Alice finishes her work. Then goes and orders the pizza, waits till it’s done. Then picks up her friend. And finally (with Bob and pizza) makes it home and gets down to the movie. It will be the sync approach and it will be way too long, so that probably Alice will have wanted to call the thing off by that time.
Alice orders his pizza online, phones Bob, invites him to come. She heads home, has her pizza delivered and starts watching the movie (and eating the pizza) without waiting for Bob to show up. That is what can happen with the async approach.
Alice orders pizza, phones Bob, invites him to come, heads home, and gets his pizza delivered. But this time, he waits until Bob comes and only after that he turns the movie on. This is what the reactive approach is about. You wait till all async actions (changes) are completed and then proceed with further actions.
Here’s a real-life example. Say, it’s Friday and John wants to spend this evening with his friend Bob, scarfing pizza and watching one of the Star Wars episodes. Let’s outline the options he has.
Reactive is a set of design principles
a way of thinking about systems architecture and design in a distributed environment where implementation techniques, tooling, and design patterns are components of a larger whole—a system.
It’s possible to write a single application in a reactive style (i.e. using reactive programming); however, that’s merely one piece of the puzzle. Though each of the above aspects may seem to qualify as “reactive,” in and of themselves they do not make a system reactive.
At the beginning always sits a data producer, the keyboard in that case. At the end of the data flow, represented by the tubes above, there is a subscriber/listener. The subscriber/listener wants to act on the result, for example, to update the UI of your software. In between, you have connectors, also called Operators: Here we have a filter connector and a map connector that manipulate data when it flows through the tube. That’s how you model all of your data flows if you use libraries like RxJava.
Publisher - A Publisher is a provider of a potentially unbounded number of elements.
Subscriber - A Subscriber listens to that Publisher, asking for new data. Sometimes, it's also referred to as a Consumer.
Backpressure - The ability of the Subscriber to let the Publisher how many requests can it handle at the time. So it's the Subscriber that is responsible for the flow of the data, not the Publisher as it just provides the data.
Flux - is a publisher that produces 0 to N values. It could be unbounded. Operations that return multiple elements use this type.
Mono - is a publisher that produces 0 to 1 value. Operations that return a single element use this type.
Suppose we had a publisher that returned more than a thousand records, or even more. Think of what the framework has to do. It's given an object of type Greeting, which it has to convert to JSON for the end user.
Had we used the traditional approach with Spring MVC, these objects would keep on accumulating in your RAM and once it collects everything it would return it to the client. This might exceed our RAM capacity and also blocks any other operation from getting processed in the meantime.
When we use Spring Webflux, the whole internal dynamics get changed. The framework starts subscribing to these records from the publisher and it serializes each item and sends it back to the client in chunks.
We do things asynchronously without creating too many threads and reusing the threads that are waiting for something. The best part it is that you don't have to do anything extra for this. In traditional Spring MVC, we could achieve the same by returning AsyncResult, DefferedResult, etc. to get some asynchronicity, but internally Spring MVC had to create a new Thread, which gets blocked since it has to wait.
These events allow a web page to get updates from a server in real-time.
It's advised to use Flux and Mono over Publisher. Both of these classes are implementations of the Publisher interface originating from Reactive Streams. While you can use them interchangeably, it's more expressive and descriptive to use the implementations.
.delayElements()- This method delays each element of the Flux by the given duration
.zip() - We're defining a Flux to generate events, and a Flux to generate values each second. By zipping them together, we get a Flux generating events each second.
We initialized a list of type Person and based on the id passed to our mapping, we filter that person out using a stream.
You might be alarmed by the usage of Thread.sleep() here, though it's just used to simulate network lag of 2 seconds.
As expected it took a little over 10 secs and this is how Spring MVC works by default.
At this day and age, waiting for a little over 10 seconds for a result on a page is unacceptable. This is the difference between keeping a customer/client and losing it due to waiting for too long.
Spring Reactor introduced a new web client to make web requests called WebClient. Compared to RestTemplate, this client has a more functional feel and is fully reactive. It's included in the spring-boot-starter-weblux dependency and it's build to replace RestTemplate in a non-blocking way.
Let's rewrite the same controller, this time, using WebClient:
This means that the thread will block until the web client receives the response. The problem with the blocking code is due to each thread consuming some amount of memory and CPU cycles.
Let's consider having a lot of incoming requests, which are waiting for some slow service needed to produce the result.
Sooner or later, the requests waiting for the results will pile up. Consequently, the application will create many threads, which will exhaust the thread pool or occupy all the available memory. We can also experience performance degradation because of the frequent CPU context (thread) switching.
Here, we created a WebClient by passing the baseUrl. Then in the main method, we simply call the endpoint.
get() indicates that we are making a GET request. We know that the response will be a single object, so we're using a Mono as explained before.
Ultimately, we asked Spring to map the response to a Person class:
And nothing happened, as expected.
If you look closely at the threads, Reactor is reusing them rather than creating new ones. This is really important if your application handles many requests in a short span of time.