Reactive Programming in Java: How, Why, and Is It Worth Doing? Part II. Reactive approach
Reactive approach
Let’s take a look at another example: suppose we are developing a UI and need to track double clicks. A triple click will be viewed as a double click.
Clicks here are a thread of mouse clicks (1, 2, 1, 3 in the scheme). We need to group them. To do that, we can use a throttle operator. If two events (two clicks) occurred within 250ms, they should be grouped. The second scheme shows such grouped values (1, 2, 1, 3). This is a thread of data, but already processed — in this case, grouped.
The initial thread was transformed into another one. Then we need to get the list length (1, 2, 1, 3). Apply filter, leaving only those values that are higher or equal to 2. In the scheme below, only two elements (2, 3) are left — these were double clicks. So we have transformed the initial thread into a thread of double clicks.
This is reactive programming: we have input threads, pass them through handlers, and get an output thread. In that case, all processing is done asynchronously, i.e., nobody waits for anyone.
Another good metaphor is a water supply system: there are pipes, one connected to another, some faucets, maybe purifiers, heaters, or coolers (these are operators), the pipes are split or united. The system works. Water is flowing. The same thing happens in reactive programming, but instead of water flow, we have data flows.
We can think up a stream-based process of cooking a soup. For example, the task is to cook as much soup as efficiently as possible. Usually, you take a cooking pot, pour some water into it, cut vegetables, etc. It’s not streaming, but a traditional approach where you cook a portion of soup. You cooked that pot, get the next one, then another one. Therefore, you have to wait until the water boils again, salt and spices dissolve, and so on. All this takes time.
Imagine another method: in a pipe (big enough to fill a pot), the water is heated to a required temperature, there are sliced vegetables. They come whole at the input and slices at the output. At some point, everything is mixed up, and the water is salted, etc. This is the most efficient cooking, a kind of super conveyor. That’s the idea of the reactive approach.
Observable example
Now, look at the code used to publish events:
Observable.just allows to put several values into a thread. And if ordinary reactive threads contain values extended in time, here we put them all at once. i.e., synchronously. In our example, these are the names of cities to which we can subscribe.
The girl (Publisher) published those values, and Observers subscribe to them and print values from the thread.
It looks like data streams in Java 8. They both are synchronous streams. Both here and in Java 8, we already know the list of values. But if we had used a standard Java 8 stream, we would not be able to add anything to it. You cannot add anything to a stream — it’s synchronous. In our example, the threads are asynchronous, which means that a new event could appear there at any time. If a training center is opened in a new location during a year, it can be added to the thread, and reactive operators will handle this properly. We added events and subscribed to them at once:
locations.subscribe(s -> System.out.println(s)))
We can at any time add a value that will be printed after a while. When a new value appears, we ask it to print and put out a list of values.
In that case, we can not only indicate what should happen when new values appear but also handle such scenarios as errors in a data stream or end of a data stream. Yes, although data streams are often endless (as in the case of a heat sensor or smoke detector, many streams can end: for example, a data stream from a server or microservice. At some point, the server closes the connection, and there is a need to react to it somehow.