Hello dear students, in this lesson we are going to learn what reactive programming is and how we can
apply reactive programming practices using Java via flow API.
We are going to start the lesson from learning general concepts.
We'll learn what reactive programming approach is.
We'll discuss what the most popular libraries for reactive programming in Java are.
But in scope of this lesson, we will focus our attention on the flow API that is available in JDK for
reactive programming.
As we will go.
We'll learn different terms like for example Backpressure aware concept.
We'll learn key interfaces from flow API.
Then we'll talk about use cases.
And I will try to answer the question when it is recommended to use reactive programming approach.
And at the end of the lesson we are going to review code examples in order to understand how reactive
streams work on practice.
Let's start our lesson.
Let's start from learning of what reactive programming is.
Reactive programming is a programming paradigm focused on handling asynchronous data streams and the
propagation of changes.
It provides a set of principles, patterns, and libraries that enable developers to build responsive
and scalable systems, especially in the context of handling events, input output operations, and
real time data.
Let's review key characteristics of reactive programming.
They include asynchronous and non-blocking.
Reactive programming emphasizes asynchronous and non-blocking operations.
It allows components to continue processing other tasks while waiting for asynchronous operations to
complete, leading to more efficient resource utilization.
Data streams.
At the core of reactive programming is the concept of data streams.
Sequences of events or values that occur over time.
These streams can represent various types of data such as user inputs, sensor readings, or messages
from a server.
Event-driven reactive systems are often event driven, meaning they respond to events or changes in
the system.
Events can trigger reactions and the system adapts dynamically to these changes.
Back pressure.
Back pressure is a key concept in reactive programming.
It refers to the ability of downstream component to signal its capacity to handle data to an upstream
component.
This helps prevent overwhelming the system with more data than it can process efficiently.
Responsive and scalable.
Reactive programming aims to create responsive applications that can handle varied workloads.
By embracing asynchrony and scalability, reactive systems can efficiently adapt to changes in demand.
Functional programming.
Reactive programming often aligns with functional programming principles.
It emphasizes immutability, pure functions, and the use of higher order functions, enabling developers
to write more concise and maintainable code.
Reactive extensions.
Reactive extensions, often referred to as RCS, is a set of libraries that provide a uniform way to
work with reactive programming concepts across multiple programming languages.
RCS libraries exist for languages like Java, JavaScript, C, sharp, and more.
Reactive programming has become popular in building modern applications, especially those that involve
real time data, user interfaces, and distributed systems.
Frameworks and libraries like Project Reactor, Erik's Java provide tools and abstractions to implement
reactive programming in various programming languages.
These tools simplify the handling of asynchronous and streaming data, making it easier for developers
to build responsive and scalable applications.
In Java, reactive programming approach may be implemented with different libraries, for example a
Java reactor core.
Reactive streams are socket martini reactor, but starting from Java version nine, you can use JDK
to implement reactive programming approach.
Flow API is available starting from Java nine.
In this lesson we will focus on flow API.
So let's learn what a flow API is.
The flow API, also known as Reactive Streams, was introduced in Java nine to address challenges associated
with asynchronous programming and handling streams of data in a more efficient and scalable way.
Reactive streams provide a standard for asynchronous stream processing with non-blocking back pressure,
enabling better control over the flow of data between components.
Reactive streams define a set of interfaces, classes, and masses that standardize the exchange of
asynchronous data streams between components in a non-blocking, back pressure aware manner.
Now let me share with you key interfaces in the flow API.
So the main interfaces include publisher, subscriber, subscription and processor.
I will make just a brief overview of them and we will learn more during the review of real life example.
Publisher amidst a potentially unbounded sequence of elements to one or more subscribers.
Subscriber consumes elements from a publisher and signals when it can handle more elements.
Subscription represents the link between a subscriber and a publisher, allowing the subscriber to request
and cancel elements.
Processor.
The processor interface in the Reactive Streams API serves as a dual role component, acting both as
a subscriber and a publisher, enabling custom data processing within a reactive stream.
So let's try to answer the question why flow API is needed?
Traditional stream processing in Java lacked a standardized way to handle asynchronous data streams,
which could lead to issues like buffer overflow or excessive resource consumption.
Reactive streams address these problems by introducing Backpressure, allowing subscribers to signal
when they are ready to receive more data, preventing overwhelming downstream components.
And in which cases it is recommended to use flow API.
Asynchronous processing.
Reactive streams are beneficial in scenarios where asynchronous processing is required, such as handling
events, input output operations, or real time data.
Backpressure handling when dealing with data streams, where the rate of data production is different
from the rate of data consumption.
Reactive streams help manage backpressure, preventing overwhelming downstream components.
Scalable and responsive applications.
Reactive programming was.
Reactive Streams is well suited for building responsive and scalable applications, especially in scenarios
like web services, where responsiveness is crucial.
Use reactive streams when you need to.
Handle asynchronous data streams.
Implement Backpressure to control the flow of data and prevent resource exhaustion.
Build scalable and responsive applications with non-blocking input output operations.
Reactive streams have gained popularity in modern Java development and are widely used in frameworks
like Project Reactor to build reactive and resilient systems.
I believe we learned enough theory, and now we are ready to review examples that I prepared especially
for this lesson.
You can find the source code of examples that I am going to share with you in attachments to the lesson.
Remember, Tim, that you are always welcome to ask questions in case something is not clear.
Just post your question below the video and I will be happy to answer.
As you can see, examples related to reactive streams are in flow API package.
We're going to start review from the flow API example class.
This is a class where I put client code and main method.
So we will go together with the flow of execution.
And I will explain you what is happening here line by line.
Let me also run this file in order to have all console output printed.
And you will be able to see the sequence of events that is happening here.
Pay attention that today we will review types that are stored on the Java.util.concurrent package inside
final class flow.
There are such important interfaces as publisher, subscriber, subscription and processor.
These are types that we are going to use today.
I start example from creation of the submission publisher.
The submission publisher is a built in implementation of the publisher interface in Java Flow API.
It emits a potentially unbounded sequence of elements, in this case integers to one or more subscribers.
The next thing I create instance of my subscriber.
It is parametrized by integer type because in this example I plan to receive integer elements.
To implement subscriber interface, you need to give implementation to all following methods.
Let's review each of them one by one.
Unsubscribe method.
This method signals about the subscription sets the subscription object, and requests the first item
to initialize the data flow with the provided subscription.
Basically, with the request method, you create demand for elements that subscriber can process depending
on the amount of requested elements, the same amount of times on next method will be invoked.
On next method.
This method processes in common integer items.
In our example, I just double all in common integers and transform them to string just for the sake
of example, because theoretically we can implement processor interface that I am going to explain a
little bit later, and you can supply process data downstream in such way you can build processing chain
and data flow.
That's why I created submit method just for the sake of example.
And just to show you that in submit method, you can put the logic of processed item submission downstream
in simple words.
Send processed item further down the processing chain depending on your business logic.
Onerror method handles errors during processing by printing an error message, allowing the processor
to respond appropriately to error conditions.
On complete signals, the completion of processing.
Printing a message to indicate the end of the data stream.
If you build a longer processing chain, this is the method where you can notify downstream components
that processing is completed, and in this method I cancel subscription.
Let's get back to flow API example class.
The next thing that I do here I subscribe my subscriber to publisher in the context of the Reactive
Streams API.
The relationship between a publisher and a subscriber is established through the subscribe method.
However, it is important to note that it is the publisher who initiates the subscription process by
calling the subscribe method on the publisher object.
Now there will be a connection between them.
Publisher will be aware about subscriber and when this will happen, then unsubscribe method will be
triggered and I will initialize my subscription property inside.
But wait, something should be wrong here.
Where missing subscription object because we have publisher, we have subscriber.
But where is subscription object?
You can implement it by yourself by implementing subscription interface.
But what is happening in this case?
You can see that I decided to print subscription object to console in order to understand the type of
this object.
And we can see that we received here.
Object of type buffered subscription.
This is nested class inside submission publisher class.
But who created this object?
And when a buffered subscription is created when a subscriber subscribes to a submission publisher,
this subscription object manages the communication between the publisher and the subscriber, facilitating
the flow of data.
The charm buffer.
It suggests that this subscription might have some internal buffer to store items when the subscriber
can't keep up with the rate of data production.
Here is a breakdown of what happens during the subscription process.
Publisher calls subscribe method and subscribe subscriber object.
Submission publisher creates a buffered subscription object internally.
This is happening inside subscribe method.
If we would open the source code of subscribe method, we will see how instance of buffered subscription
is created.
Unsubscribe method is called on the subscriber with created buffered subscription.
The unsubscribe method is then invoked on the subscriber, passing the buffered subscription as an argument.
This allows the subscriber to request items and manage backpressure.
The buffered aspect of the subscription typically comes into play when the subscriber is unable to keep
up with the rate of data production.
In such cases, the buffered subscription may buffer items until the subscriber signals that it can
process more data by calling the request method.
So after subscription is happened, then I set the timeout.
We need this time out to make sure that subscription is created.
But using timeout is very primitive way that I decided to use for the sake of example.
In reality, ensuring that everyone is successfully subscribed in a reactive system can be a complicated
task, and the approach might depend on the specific requirements of your application.
Some points to consider using of countdown Lodge.
Using of completablefuture.
Using of timeout mechanism, including using it with completablefuture.
In my course Java from zero to first job.
I have multi-threading section where I explained in details API of countdown, Lodge, Completablefuture
and other things.
That's why I wouldn't stop too much on this right now.
In console you can see subscriber subscribed.
After the timeout, I declare a for loop with counter and I submit five integers using publisher object.
I submit them one by one, and in console you can see that publisher submits one element and subscriber
receives it.
Taking into account that println method is synchronized, text and console not necessarily represents
the exact sequence of events, but at least it can give us an understanding of what is going on.
Subscriber after receiving integers, doubles, received elements and prints them to console two and
when publisher is closed, subscriber got notified two and Oncomplete method is triggered.
And that's it.
Knowing this, you can build more complex data flows with multiple subscribers, different publishers.
You can connect them between each other as your business logic would require.
And the last but not the least thing that I would like to show you for today is processor interface.
You can create processor class that would implement processor interface if you want to make it acting
as both a publisher and a subscriber.
This can be helpful when you want to build a processing chain.
For example, you can receive some data from publisher, process it, and pass it further to the next
subscriber.
And like this.
You can build complex data flows.
So the processor interface in the Reactive Streams API is used when you need to create a component that
acts as a both a publisher and a subscriber.
It is particularly useful in scenarios where you want to transform or process the data flowing through
a reactive stream before it reaches the downstream subscribers.
Here are some situations where you might consider using the processor interface.
Transforming or processing data.
If you need to apply transformations, filtering or any other processing logic to the data before it
is delivered to subscribers, the processor interface allows you to implement this logic.
Connecting different parts of a reactive pipeline.
When you have a reactive pipeline with multiple stages, and you want to connect these stages with a
component that can both consume data from the upstream and emit processed data to the downstream.
Implementing a custom flow control mechanism.
If you need to implement a custom flow control mechanism between the publisher and subscribers, the
processor allows you to manage backpressure and control the rate at which data is consumed.
Aggregating or combining data streams.
When you want to combine or aggregate data from multiple publishers before delivering it to downstream
subscribers.
A processor can be used to implement the necessary logic.
If you check the source code of processor interface, then you will find that processor is a static
interface that extends subscriber and publisher at the same time.
In Java, an interface can extend multiple other interfaces.
This feature allows an interface to inherit abstract masses from multiple parent interfaces, providing
a way to express multiple types of behavior in a single interface.
So feel free to use processor interface implementations when it will be needed in your business cases.
That's all what I wanted to share with you today in this lesson.
Let's recap what we have learned in the video.
We learned reactive programming approach.
Now you know the most popular libraries for reactive programming in Java.
I explained you what flow API in JDK is, and we learned key interfaces and types in flow API.
I explained when and in which cases it is recommended to use Reactive Programming, Reactive Streams
flow API, and at the end of the lesson we reviewed code example in order to understand how it works
in practice.
That's all what I wanted to share with you today.
Have a great day and see you in the next lesson.