java-development-for-beginners-learnit / 31 - JDK API /003 Reactive Programming in Java Flow API, Reactive Streams_en.srt
Tan115's picture
Add files using upload-large-folder tool
07c2d26 verified
Raw
History Blame Contribute Delete
25.5 kB
1
00:00:05,000 --> 00:00:11,000
Hello dear students, in this lesson we are going to learn what reactive programming is and how we can
2
00:00:11,000 --> 00:00:16,000
apply reactive programming practices using Java via flow API.
3
00:00:16,000 --> 00:00:20,000
We are going to start the lesson from learning general concepts.
4
00:00:20,000 --> 00:00:23,000
We'll learn what reactive programming approach is.
5
00:00:23,000 --> 00:00:29,000
We'll discuss what the most popular libraries for reactive programming in Java are.
6
00:00:29,000 --> 00:00:36,000
But in scope of this lesson, we will focus our attention on the flow API that is available in JDK for
7
00:00:36,000 --> 00:00:38,000
reactive programming.
8
00:00:38,000 --> 00:00:39,000
As we will go.
9
00:00:39,000 --> 00:00:45,000
We'll learn different terms like for example Backpressure aware concept.
10
00:00:45,000 --> 00:00:49,000
We'll learn key interfaces from flow API.
11
00:00:49,000 --> 00:00:51,000
Then we'll talk about use cases.
12
00:00:51,000 --> 00:00:57,000
And I will try to answer the question when it is recommended to use reactive programming approach.
13
00:00:57,000 --> 00:01:04,000
And at the end of the lesson we are going to review code examples in order to understand how reactive
14
00:01:04,000 --> 00:01:06,000
streams work on practice.
15
00:01:06,000 --> 00:01:08,000
Let's start our lesson.
16
00:01:09,000 --> 00:01:12,000
Let's start from learning of what reactive programming is.
17
00:01:13,000 --> 00:01:20,000
Reactive programming is a programming paradigm focused on handling asynchronous data streams and the
18
00:01:20,000 --> 00:01:22,000
propagation of changes.
19
00:01:22,000 --> 00:01:29,000
It provides a set of principles, patterns, and libraries that enable developers to build responsive
20
00:01:29,000 --> 00:01:36,000
and scalable systems, especially in the context of handling events, input output operations, and
21
00:01:36,000 --> 00:01:37,000
real time data.
22
00:01:38,000 --> 00:01:41,000
Let's review key characteristics of reactive programming.
23
00:01:41,000 --> 00:01:45,000
They include asynchronous and non-blocking.
24
00:01:46,000 --> 00:01:51,000
Reactive programming emphasizes asynchronous and non-blocking operations.
25
00:01:51,000 --> 00:01:58,000
It allows components to continue processing other tasks while waiting for asynchronous operations to
26
00:01:58,000 --> 00:02:02,000
complete, leading to more efficient resource utilization.
27
00:02:03,000 --> 00:02:04,000
Data streams.
28
00:02:04,000 --> 00:02:09,000
At the core of reactive programming is the concept of data streams.
29
00:02:09,000 --> 00:02:13,000
Sequences of events or values that occur over time.
30
00:02:13,000 --> 00:02:21,000
These streams can represent various types of data such as user inputs, sensor readings, or messages
31
00:02:21,000 --> 00:02:22,000
from a server.
32
00:02:23,000 --> 00:02:30,000
Event-driven reactive systems are often event driven, meaning they respond to events or changes in
33
00:02:30,000 --> 00:02:31,000
the system.
34
00:02:31,000 --> 00:02:37,000
Events can trigger reactions and the system adapts dynamically to these changes.
35
00:02:38,000 --> 00:02:39,000
Back pressure.
36
00:02:40,000 --> 00:02:43,000
Back pressure is a key concept in reactive programming.
37
00:02:43,000 --> 00:02:51,000
It refers to the ability of downstream component to signal its capacity to handle data to an upstream
38
00:02:51,000 --> 00:02:52,000
component.
39
00:02:52,000 --> 00:02:59,000
This helps prevent overwhelming the system with more data than it can process efficiently.
40
00:03:00,000 --> 00:03:02,000
Responsive and scalable.
41
00:03:02,000 --> 00:03:09,000
Reactive programming aims to create responsive applications that can handle varied workloads.
42
00:03:09,000 --> 00:03:17,000
By embracing asynchrony and scalability, reactive systems can efficiently adapt to changes in demand.
43
00:03:18,000 --> 00:03:20,000
Functional programming.
44
00:03:20,000 --> 00:03:25,000
Reactive programming often aligns with functional programming principles.
45
00:03:25,000 --> 00:03:33,000
It emphasizes immutability, pure functions, and the use of higher order functions, enabling developers
46
00:03:33,000 --> 00:03:36,000
to write more concise and maintainable code.
47
00:03:37,000 --> 00:03:39,000
Reactive extensions.
48
00:03:39,000 --> 00:03:47,000
Reactive extensions, often referred to as RCS, is a set of libraries that provide a uniform way to
49
00:03:47,000 --> 00:03:52,000
work with reactive programming concepts across multiple programming languages.
50
00:03:53,000 --> 00:03:58,000
RCS libraries exist for languages like Java, JavaScript, C, sharp, and more.
51
00:03:59,000 --> 00:04:05,000
Reactive programming has become popular in building modern applications, especially those that involve
52
00:04:05,000 --> 00:04:10,000
real time data, user interfaces, and distributed systems.
53
00:04:10,000 --> 00:04:18,000
Frameworks and libraries like Project Reactor, Erik's Java provide tools and abstractions to implement
54
00:04:18,000 --> 00:04:21,000
reactive programming in various programming languages.
55
00:04:22,000 --> 00:04:29,000
These tools simplify the handling of asynchronous and streaming data, making it easier for developers
56
00:04:29,000 --> 00:04:32,000
to build responsive and scalable applications.
57
00:04:33,000 --> 00:04:40,000
In Java, reactive programming approach may be implemented with different libraries, for example a
58
00:04:40,000 --> 00:04:42,000
Java reactor core.
59
00:04:42,000 --> 00:04:52,000
Reactive streams are socket martini reactor, but starting from Java version nine, you can use JDK
60
00:04:52,000 --> 00:04:55,000
to implement reactive programming approach.
61
00:04:55,000 --> 00:04:59,000
Flow API is available starting from Java nine.
62
00:04:59,000 --> 00:05:02,000
In this lesson we will focus on flow API.
63
00:05:03,000 --> 00:05:05,000
So let's learn what a flow API is.
64
00:05:06,000 --> 00:05:14,000
The flow API, also known as Reactive Streams, was introduced in Java nine to address challenges associated
65
00:05:14,000 --> 00:05:21,000
with asynchronous programming and handling streams of data in a more efficient and scalable way.
66
00:05:21,000 --> 00:05:28,000
Reactive streams provide a standard for asynchronous stream processing with non-blocking back pressure,
67
00:05:28,000 --> 00:05:33,000
enabling better control over the flow of data between components.
68
00:05:33,000 --> 00:05:40,000
Reactive streams define a set of interfaces, classes, and masses that standardize the exchange of
69
00:05:40,000 --> 00:05:46,000
asynchronous data streams between components in a non-blocking, back pressure aware manner.
70
00:05:47,000 --> 00:05:51,000
Now let me share with you key interfaces in the flow API.
71
00:05:51,000 --> 00:05:58,000
So the main interfaces include publisher, subscriber, subscription and processor.
72
00:05:58,000 --> 00:06:05,000
I will make just a brief overview of them and we will learn more during the review of real life example.
73
00:06:06,000 --> 00:06:13,000
Publisher amidst a potentially unbounded sequence of elements to one or more subscribers.
74
00:06:14,000 --> 00:06:22,000
Subscriber consumes elements from a publisher and signals when it can handle more elements.
75
00:06:22,000 --> 00:06:30,000
Subscription represents the link between a subscriber and a publisher, allowing the subscriber to request
76
00:06:30,000 --> 00:06:31,000
and cancel elements.
77
00:06:32,000 --> 00:06:33,000
Processor.
78
00:06:34,000 --> 00:06:43,000
The processor interface in the Reactive Streams API serves as a dual role component, acting both as
79
00:06:43,000 --> 00:06:49,000
a subscriber and a publisher, enabling custom data processing within a reactive stream.
80
00:06:50,000 --> 00:06:55,000
So let's try to answer the question why flow API is needed?
81
00:06:55,000 --> 00:07:02,000
Traditional stream processing in Java lacked a standardized way to handle asynchronous data streams,
82
00:07:02,000 --> 00:07:09,000
which could lead to issues like buffer overflow or excessive resource consumption.
83
00:07:09,000 --> 00:07:16,000
Reactive streams address these problems by introducing Backpressure, allowing subscribers to signal
84
00:07:16,000 --> 00:07:22,000
when they are ready to receive more data, preventing overwhelming downstream components.
85
00:07:22,000 --> 00:07:26,000
And in which cases it is recommended to use flow API.
86
00:07:27,000 --> 00:07:29,000
Asynchronous processing.
87
00:07:29,000 --> 00:07:36,000
Reactive streams are beneficial in scenarios where asynchronous processing is required, such as handling
88
00:07:36,000 --> 00:07:40,000
events, input output operations, or real time data.
89
00:07:41,000 --> 00:07:48,000
Backpressure handling when dealing with data streams, where the rate of data production is different
90
00:07:48,000 --> 00:07:50,000
from the rate of data consumption.
91
00:07:50,000 --> 00:07:56,000
Reactive streams help manage backpressure, preventing overwhelming downstream components.
92
00:07:57,000 --> 00:08:00,000
Scalable and responsive applications.
93
00:08:01,000 --> 00:08:02,000
Reactive programming was.
94
00:08:02,000 --> 00:08:09,000
Reactive Streams is well suited for building responsive and scalable applications, especially in scenarios
95
00:08:09,000 --> 00:08:13,000
like web services, where responsiveness is crucial.
96
00:08:14,000 --> 00:08:16,000
Use reactive streams when you need to.
97
00:08:17,000 --> 00:08:20,000
Handle asynchronous data streams.
98
00:08:20,000 --> 00:08:26,000
Implement Backpressure to control the flow of data and prevent resource exhaustion.
99
00:08:26,000 --> 00:08:34,000
Build scalable and responsive applications with non-blocking input output operations.
100
00:08:35,000 --> 00:08:41,000
Reactive streams have gained popularity in modern Java development and are widely used in frameworks
101
00:08:41,000 --> 00:08:46,000
like Project Reactor to build reactive and resilient systems.
102
00:08:46,000 --> 00:08:52,000
I believe we learned enough theory, and now we are ready to review examples that I prepared especially
103
00:08:52,000 --> 00:08:53,000
for this lesson.
104
00:08:54,000 --> 00:09:00,000
You can find the source code of examples that I am going to share with you in attachments to the lesson.
105
00:09:00,000 --> 00:09:06,000
Remember, Tim, that you are always welcome to ask questions in case something is not clear.
106
00:09:06,000 --> 00:09:11,000
Just post your question below the video and I will be happy to answer.
107
00:09:11,000 --> 00:09:17,000
As you can see, examples related to reactive streams are in flow API package.
108
00:09:18,000 --> 00:09:22,000
We're going to start review from the flow API example class.
109
00:09:22,000 --> 00:09:26,000
This is a class where I put client code and main method.
110
00:09:27,000 --> 00:09:30,000
So we will go together with the flow of execution.
111
00:09:30,000 --> 00:09:34,000
And I will explain you what is happening here line by line.
112
00:09:34,000 --> 00:09:39,000
Let me also run this file in order to have all console output printed.
113
00:09:39,000 --> 00:09:43,000
And you will be able to see the sequence of events that is happening here.
114
00:09:44,000 --> 00:09:51,000
Pay attention that today we will review types that are stored on the Java.util.concurrent package inside
115
00:09:51,000 --> 00:09:53,000
final class flow.
116
00:09:53,000 --> 00:10:00,000
There are such important interfaces as publisher, subscriber, subscription and processor.
117
00:10:00,000 --> 00:10:03,000
These are types that we are going to use today.
118
00:10:04,000 --> 00:10:07,000
I start example from creation of the submission publisher.
119
00:10:08,000 --> 00:10:15,000
The submission publisher is a built in implementation of the publisher interface in Java Flow API.
120
00:10:15,000 --> 00:10:23,000
It emits a potentially unbounded sequence of elements, in this case integers to one or more subscribers.
121
00:10:24,000 --> 00:10:29,000
The next thing I create instance of my subscriber.
122
00:10:29,000 --> 00:10:36,000
It is parametrized by integer type because in this example I plan to receive integer elements.
123
00:10:36,000 --> 00:10:42,000
To implement subscriber interface, you need to give implementation to all following methods.
124
00:10:42,000 --> 00:10:45,000
Let's review each of them one by one.
125
00:10:46,000 --> 00:10:48,000
Unsubscribe method.
126
00:10:48,000 --> 00:10:56,000
This method signals about the subscription sets the subscription object, and requests the first item
127
00:10:56,000 --> 00:10:59,000
to initialize the data flow with the provided subscription.
128
00:11:00,000 --> 00:11:07,000
Basically, with the request method, you create demand for elements that subscriber can process depending
129
00:11:07,000 --> 00:11:13,000
on the amount of requested elements, the same amount of times on next method will be invoked.
130
00:11:14,000 --> 00:11:16,000
On next method.
131
00:11:16,000 --> 00:11:21,000
This method processes in common integer items.
132
00:11:21,000 --> 00:11:28,000
In our example, I just double all in common integers and transform them to string just for the sake
133
00:11:28,000 --> 00:11:34,000
of example, because theoretically we can implement processor interface that I am going to explain a
134
00:11:34,000 --> 00:11:42,000
little bit later, and you can supply process data downstream in such way you can build processing chain
135
00:11:42,000 --> 00:11:43,000
and data flow.
136
00:11:44,000 --> 00:11:48,000
That's why I created submit method just for the sake of example.
137
00:11:48,000 --> 00:11:55,000
And just to show you that in submit method, you can put the logic of processed item submission downstream
138
00:11:55,000 --> 00:11:56,000
in simple words.
139
00:11:56,000 --> 00:12:03,000
Send processed item further down the processing chain depending on your business logic.
140
00:12:04,000 --> 00:12:12,000
Onerror method handles errors during processing by printing an error message, allowing the processor
141
00:12:12,000 --> 00:12:15,000
to respond appropriately to error conditions.
142
00:12:16,000 --> 00:12:20,000
On complete signals, the completion of processing.
143
00:12:20,000 --> 00:12:24,000
Printing a message to indicate the end of the data stream.
144
00:12:24,000 --> 00:12:31,000
If you build a longer processing chain, this is the method where you can notify downstream components
145
00:12:31,000 --> 00:12:36,000
that processing is completed, and in this method I cancel subscription.
146
00:12:37,000 --> 00:12:40,000
Let's get back to flow API example class.
147
00:12:41,000 --> 00:12:49,000
The next thing that I do here I subscribe my subscriber to publisher in the context of the Reactive
148
00:12:49,000 --> 00:12:50,000
Streams API.
149
00:12:50,000 --> 00:12:57,000
The relationship between a publisher and a subscriber is established through the subscribe method.
150
00:12:57,000 --> 00:13:04,000
However, it is important to note that it is the publisher who initiates the subscription process by
151
00:13:04,000 --> 00:13:08,000
calling the subscribe method on the publisher object.
152
00:13:08,000 --> 00:13:11,000
Now there will be a connection between them.
153
00:13:12,000 --> 00:13:19,000
Publisher will be aware about subscriber and when this will happen, then unsubscribe method will be
154
00:13:19,000 --> 00:13:24,000
triggered and I will initialize my subscription property inside.
155
00:13:25,000 --> 00:13:28,000
But wait, something should be wrong here.
156
00:13:28,000 --> 00:13:34,000
Where missing subscription object because we have publisher, we have subscriber.
157
00:13:34,000 --> 00:13:36,000
But where is subscription object?
158
00:13:36,000 --> 00:13:41,000
You can implement it by yourself by implementing subscription interface.
159
00:13:41,000 --> 00:13:43,000
But what is happening in this case?
160
00:13:44,000 --> 00:13:50,000
You can see that I decided to print subscription object to console in order to understand the type of
161
00:13:50,000 --> 00:13:51,000
this object.
162
00:13:52,000 --> 00:13:54,000
And we can see that we received here.
163
00:13:54,000 --> 00:13:56,000
Object of type buffered subscription.
164
00:13:57,000 --> 00:14:01,000
This is nested class inside submission publisher class.
165
00:14:01,000 --> 00:14:03,000
But who created this object?
166
00:14:03,000 --> 00:14:10,000
And when a buffered subscription is created when a subscriber subscribes to a submission publisher,
167
00:14:11,000 --> 00:14:17,000
this subscription object manages the communication between the publisher and the subscriber, facilitating
168
00:14:17,000 --> 00:14:19,000
the flow of data.
169
00:14:19,000 --> 00:14:21,000
The charm buffer.
170
00:14:21,000 --> 00:14:28,000
It suggests that this subscription might have some internal buffer to store items when the subscriber
171
00:14:28,000 --> 00:14:31,000
can't keep up with the rate of data production.
172
00:14:32,000 --> 00:14:36,000
Here is a breakdown of what happens during the subscription process.
173
00:14:37,000 --> 00:14:42,000
Publisher calls subscribe method and subscribe subscriber object.
174
00:14:42,000 --> 00:14:47,000
Submission publisher creates a buffered subscription object internally.
175
00:14:48,000 --> 00:14:51,000
This is happening inside subscribe method.
176
00:14:51,000 --> 00:14:57,000
If we would open the source code of subscribe method, we will see how instance of buffered subscription
177
00:14:57,000 --> 00:14:58,000
is created.
178
00:14:59,000 --> 00:15:05,000
Unsubscribe method is called on the subscriber with created buffered subscription.
179
00:15:05,000 --> 00:15:12,000
The unsubscribe method is then invoked on the subscriber, passing the buffered subscription as an argument.
180
00:15:12,000 --> 00:15:17,000
This allows the subscriber to request items and manage backpressure.
181
00:15:17,000 --> 00:15:24,000
The buffered aspect of the subscription typically comes into play when the subscriber is unable to keep
182
00:15:24,000 --> 00:15:27,000
up with the rate of data production.
183
00:15:27,000 --> 00:15:35,000
In such cases, the buffered subscription may buffer items until the subscriber signals that it can
184
00:15:35,000 --> 00:15:38,000
process more data by calling the request method.
185
00:15:39,000 --> 00:15:44,000
So after subscription is happened, then I set the timeout.
186
00:15:45,000 --> 00:15:49,000
We need this time out to make sure that subscription is created.
187
00:15:49,000 --> 00:15:56,000
But using timeout is very primitive way that I decided to use for the sake of example.
188
00:15:56,000 --> 00:16:03,000
In reality, ensuring that everyone is successfully subscribed in a reactive system can be a complicated
189
00:16:03,000 --> 00:16:09,000
task, and the approach might depend on the specific requirements of your application.
190
00:16:10,000 --> 00:16:14,000
Some points to consider using of countdown Lodge.
191
00:16:15,000 --> 00:16:17,000
Using of completablefuture.
192
00:16:17,000 --> 00:16:22,000
Using of timeout mechanism, including using it with completablefuture.
193
00:16:23,000 --> 00:16:26,000
In my course Java from zero to first job.
194
00:16:26,000 --> 00:16:33,000
I have multi-threading section where I explained in details API of countdown, Lodge, Completablefuture
195
00:16:33,000 --> 00:16:35,000
and other things.
196
00:16:35,000 --> 00:16:38,000
That's why I wouldn't stop too much on this right now.
197
00:16:39,000 --> 00:16:43,000
In console you can see subscriber subscribed.
198
00:16:44,000 --> 00:16:52,000
After the timeout, I declare a for loop with counter and I submit five integers using publisher object.
199
00:16:52,000 --> 00:17:00,000
I submit them one by one, and in console you can see that publisher submits one element and subscriber
200
00:17:00,000 --> 00:17:01,000
receives it.
201
00:17:01,000 --> 00:17:08,000
Taking into account that println method is synchronized, text and console not necessarily represents
202
00:17:08,000 --> 00:17:15,000
the exact sequence of events, but at least it can give us an understanding of what is going on.
203
00:17:15,000 --> 00:17:23,000
Subscriber after receiving integers, doubles, received elements and prints them to console two and
204
00:17:23,000 --> 00:17:30,000
when publisher is closed, subscriber got notified two and Oncomplete method is triggered.
205
00:17:31,000 --> 00:17:32,000
And that's it.
206
00:17:32,000 --> 00:17:39,000
Knowing this, you can build more complex data flows with multiple subscribers, different publishers.
207
00:17:39,000 --> 00:17:44,000
You can connect them between each other as your business logic would require.
208
00:17:45,000 --> 00:17:51,000
And the last but not the least thing that I would like to show you for today is processor interface.
209
00:17:52,000 --> 00:17:58,000
You can create processor class that would implement processor interface if you want to make it acting
210
00:17:58,000 --> 00:18:02,000
as both a publisher and a subscriber.
211
00:18:02,000 --> 00:18:06,000
This can be helpful when you want to build a processing chain.
212
00:18:06,000 --> 00:18:13,000
For example, you can receive some data from publisher, process it, and pass it further to the next
213
00:18:13,000 --> 00:18:14,000
subscriber.
214
00:18:15,000 --> 00:18:15,000
And like this.
215
00:18:15,000 --> 00:18:18,000
You can build complex data flows.
216
00:18:18,000 --> 00:18:26,000
So the processor interface in the Reactive Streams API is used when you need to create a component that
217
00:18:26,000 --> 00:18:29,000
acts as a both a publisher and a subscriber.
218
00:18:29,000 --> 00:18:36,000
It is particularly useful in scenarios where you want to transform or process the data flowing through
219
00:18:36,000 --> 00:18:40,000
a reactive stream before it reaches the downstream subscribers.
220
00:18:41,000 --> 00:18:46,000
Here are some situations where you might consider using the processor interface.
221
00:18:47,000 --> 00:18:50,000
Transforming or processing data.
222
00:18:50,000 --> 00:18:57,000
If you need to apply transformations, filtering or any other processing logic to the data before it
223
00:18:57,000 --> 00:19:03,000
is delivered to subscribers, the processor interface allows you to implement this logic.
224
00:19:04,000 --> 00:19:07,000
Connecting different parts of a reactive pipeline.
225
00:19:07,000 --> 00:19:13,000
When you have a reactive pipeline with multiple stages, and you want to connect these stages with a
226
00:19:13,000 --> 00:19:20,000
component that can both consume data from the upstream and emit processed data to the downstream.
227
00:19:21,000 --> 00:19:24,000
Implementing a custom flow control mechanism.
228
00:19:24,000 --> 00:19:31,000
If you need to implement a custom flow control mechanism between the publisher and subscribers, the
229
00:19:31,000 --> 00:19:38,000
processor allows you to manage backpressure and control the rate at which data is consumed.
230
00:19:38,000 --> 00:19:41,000
Aggregating or combining data streams.
231
00:19:41,000 --> 00:19:47,000
When you want to combine or aggregate data from multiple publishers before delivering it to downstream
232
00:19:47,000 --> 00:19:49,000
subscribers.
233
00:19:49,000 --> 00:19:52,000
A processor can be used to implement the necessary logic.
234
00:19:53,000 --> 00:20:00,000
If you check the source code of processor interface, then you will find that processor is a static
235
00:20:00,000 --> 00:20:05,000
interface that extends subscriber and publisher at the same time.
236
00:20:05,000 --> 00:20:10,000
In Java, an interface can extend multiple other interfaces.
237
00:20:10,000 --> 00:20:17,000
This feature allows an interface to inherit abstract masses from multiple parent interfaces, providing
238
00:20:17,000 --> 00:20:22,000
a way to express multiple types of behavior in a single interface.
239
00:20:22,000 --> 00:20:30,000
So feel free to use processor interface implementations when it will be needed in your business cases.
240
00:20:30,000 --> 00:20:34,000
That's all what I wanted to share with you today in this lesson.
241
00:20:34,000 --> 00:20:37,000
Let's recap what we have learned in the video.
242
00:20:38,000 --> 00:20:41,000
We learned reactive programming approach.
243
00:20:41,000 --> 00:20:45,000
Now you know the most popular libraries for reactive programming in Java.
244
00:20:46,000 --> 00:20:54,000
I explained you what flow API in JDK is, and we learned key interfaces and types in flow API.
245
00:20:54,000 --> 00:21:01,000
I explained when and in which cases it is recommended to use Reactive Programming, Reactive Streams
246
00:21:01,000 --> 00:21:08,000
flow API, and at the end of the lesson we reviewed code example in order to understand how it works
247
00:21:08,000 --> 00:21:09,000
in practice.
248
00:21:09,000 --> 00:21:12,000
That's all what I wanted to share with you today.
249
00:21:12,000 --> 00:21:15,000
Have a great day and see you in the next lesson.