What I learned from a year of using reactive streams.

Kalpa Senanayake
6 min readAug 25, 2019
Photo by Greg Rakozy on Unsplash

You do not have to re-implement all existing APIs in reactive streams.

Photo by Kelly Sikkema on Unsplash

One can still design and implement quality systems using synchronous, blocking, and imperative programming tools. And those systems will continue to be a vital part of the API ecosystem for the foreseeable future.

The most crucial part here is the decision to use reactive streams for the API implementation should be data-driven. What it means is that one needs to consider the following factors to identify when to use it.

When nature of the systems as follows

  1. A heavily integrated environment with lots of network latency.
  2. Failures and are inevitable, and recovery should happen fast.
  3. It consists of the slow publisher -> fast receiver and fast publisher -> slow receiver combinations.
  4. It operates in a highly concurrent state.
  5. It should be able to scale horizontally.

It is worth adding reactive streams to the recipe.

Reactive streams are the right choice to deal with asynchronously generated data.

Photo by Curtis MacNewton on Unsplash

Reactive streams have the following characteristics

  • Push based (push the data towards the subscribers, instead subscriber pull data)
  • Support sophisticated composition and transformation capability.
  • Operate in a time-aware manner.

The above qualities make it quite a powerful tool to deal with asynchronous data. The day to day integration problems can solve using short, declarative, but readable codes.

The program reacts to the events of the availability of new information.

Photo by AbsolutVision on Unsplash

When someone introduced to the asynchronous, non-blocking reactive world from synchronous blocking and imperative background, it takes some time to realise what is “reactive” here.

In the imperative style way, the thread of execution governs the behaviour of the system. In contrast, systems behaviour changes when new data is available. Just like we humans react to a breaking news popup message by our smart phone’s push notification.

Reactive stream APIs are concurrency agnostic.

Photo by amirali mirhashemian on Unsplash

The whole idea behind the reactive streams and the declarative approach is not to tell the program how to do it, but let it know what to do. And the low-level details like thread management, execution is abstracted away from the business logic.

However, if you want to get involved, these libraries do not stop you and provide tools to do that. For example, if one comes across an RDBMS query which block and wait, the developer can choose to delegate it to another thread.

Reactive stream sources come in two flavours, Hot and Cold.

Photo by Kelly Sikkema on Unsplash

A cold stream source will emit the whole event set to each new subscriber.

A hot stream source will emit only the newest event set form the point of connection to a new subscriber.

Reactive streams lazily evaluated.

Photo by Kate Stone Matheson on Unsplash

Intermediate operations of the stream not get evaluated eagerly. Instead, they get assessed on demand when the terminal operation got invoked.

It is hard to debug reactive APIs

In the “reactive stream” ecosystem, everything happens asynchronously. Hence when an error occurs, the information about the error is intertwined with lots of noise, and sometimes it is not so much helpful in terms of figuring out what went wrong.

But the good news is the teams behind popular reactive streams APIs (like project reactor team) are working on to makes it easy for the application developers.

Most API clients and middle layers are still not ready to consume event streams.

Photo by Ali Yahya on Unsplash

Even though all mainstream languages have reactive programming libraries, the number of clients who are ready to consume a reactive stream is limited due to architectural limitations.

For example, one can use MediaType.APPLICATION_STREAM_JSON_VALUE to emit JSON elements to client. However, the intermediate layers like API managers are yet to grasp the idea of streaming JSON elements. Hence one cannot utilise the full power comes with a publisher who can emit 0 to N elements.

They provide three channels for communication

  1. Data channel
  2. Error channel
  3. Complete channel

It is essential to handle the empty streams.

Photo by Reza Aulia on Unsplash

When dealing with reactive streams, every method should return a stream of something. But there is no guarantee that all these stream sources will emit 1 or more events. It could emit 0 events. That can easily get overlooked.

That seems not much important until one finds a scenario where the event stream is empty and another advanced operator like zipping may give unexpected results. A simple example of this is an HTTP body with Content-Length is equal to 0. That will not emit any event when try to deserialise and would not get converted into an error and might fail silently.

Use standard testing tools instead of block or other concurrency controls test reactive streams.

Photo by Louis Reed on Unsplash

Since everything happens, asynchronously, hence synchronous style testing mechanism does not offer much help this ecosystem. Thus developers usually try to block the stream until asynchronous operations get completed to verify the results. Blocking is not the sledgehammer approach to test reactive streams, and it does not represent the true nature of the code. Even for testing purpose blocking is terrible after all, it is a stream.

Another approach is to fall back to their concurrency tool kit like CounDownLatch. That also does not scale well since these tools not capable of dealing with streams. For example, an infinite event stream can not be tested using CounDownLatch.

The right way to do this is to use the test tools provided by the reactive library.

The event signal timeout is special.

Photo by Aron Visuals on Unsplash

All publishers have the concept of signal timeout. It represents the timeout which occurs if the next signal has not arrived within the given time.

This timeout may or may not have associations with other timeouts like TCP timeout depending on the stream library implementation. For example, some libraries may close the TCP connection to avoid connection leak in the event of a signal timeout.

Errors are first-class citizens of the “reactive “ world.

Photo by Michał Parzuchowski on Unsplash

In the “reactive” ecosystem, everything is a stream of messages, events, computations, query results, and even the errors are part of the flow. And every “error” is a terminal event. What this means is that imperative style concepts like checked exceptions are no longer valid here.

The subscriber will get the error as same as any other signal. It’s up to the subscriber to deal with it now or pass it on, so it will be handled somewhere else.

--

--