Introduction to Reactive Programming
Explore the fundamentals of reactive programming and how it applies to building reactive web applications with Spring Boot. Understand core concepts like Reactive Streams, backpressure, and the use of Project Reactor's Flux type to manage asynchronous data flows efficiently without blocking application threads.
We'll cover the following...
Reactive programming has existed for years. We can find academic papers on facets of it going back to the 1970s. There have been asynchronous, event-driven programming stacks available for years, too.
So, why hasn’t it been picked up in the mainstream?
This is probably because most shops haven’t needed it. The world is entering a new era, though. Startups have to serve content to millions of users, international audiences demand around-the-clock operational support, and as the cloud-based hosting of applications grows in popularity, simply adding more servers doesn’t work.
Reactive Streams
Developers seek more efficient and consistent usage of existing resources. Reactive Streams is one approach that addresses this.
Reactive Streams is a tiny spec. Its focus is to define a simple contract between publishers and subscribers. Instead of publishing traffic as fast as possible, subscribers can exert control by declaring when they’re ready for more content, and the publisher only sends the amount requested. Think of it as demand control.
Chaining together publishers and subscribers across the enterprise makes it possible to have system-wide backpressure.
The ability to overload traffic communication in software systems is known as backpressure. In other words, this is how to regulate the transmission of stream elements and control the number of elements the recipient can consume.
No more performance surprises, but instead better-coordinated management of traffic.
Reactive Streams is so simple that it isn’t recommended for application developers. Instead, it’s a foundation for frameworks and a means to achieve interoperability.
Project reactor
Project reactor is VMware’s implementation of Reactive Streams. With it, it’s possible to achieve reactive programming built with a:
-
Non-blocking, asynchronous programming model.
-
Functional programming style.
-
Thread-agnostic concurrency.
The Spring project portfolio makes it easy to embrace scalable solutions without starting from scratch.
Coding with reactor types
As mentioned, Reactive Streams is based on demand control. Project reactor implements this using the core type Flux<T>. A Flux<T> is a container around a series of T objects.
A Flux<T> is a placeholder for someone to deliver items. It’s kind of like a server at a restaurant. As dishes are completed in the kitchen, they’re handed to the server. The server then carries them out to customers and returns to wait for more.
Continuing with this metaphor, the server doesn’t know when the kitchen will finish the next dish. When a customer has placed an order and the dish is finished, the server can deliver it.
Imagine modeling a service that emulates this metaphorical kitchen:
As a server, we can ask that the KitchenService provide us with Dish objects to bring to customers. Instead of being handed a batch, we receive this Flux<Dish> object. It means that the dishes aren’t available yet, but they will be at some point. But we aren’t sure exactly when that is.
We can act, or react, once they’re ready. That’s because the reactor is non-blocking. We don’t hold up the server thread waiting for the kitchen to do its job.
This Flux<Dish> is similar to a Future<Dish> in that the results come sometime in the future. That’s about it when it comes to the similarities between Flux<Dish> and Future<Dish>. A Future object is something that has already started, while a Flux object is startable.
Note: A
Futureobject is blocking, that is, it blocks the server thread.
Characteristics of Flux<Dish>
So, what does Flux offer that Future doesn’t?
- It handles more than one
Dishin a non-blocking way. - It codifies what happens as each
Dishis provided. - It defines both success and failure paths.
- It doesn’t need to poll for results.
- It offers functional programming support.
Much of this limited functionality is tied up because Java’s Future API was introduced with Java 5, which dates back to 2004. That was long before Java introduced functional programming constructs. The other factor is that Future is squarely aimed at providing a single value, while Flux is meant for multiple values. There have been some updates to Java’s Future<T> class, but they don’t cut it when we want to implement backpressure and demand control.