Reactive Patterns: Try-Catch-Finally
--
In today’s blog post, we cover a topic that at first seems pretty trivial to programmers outside of the world of Reactive Programming. I’m talking about “try-catch-finally”.
Most programming languages offer keywords for this purpose, so it is very easy to implement and use. In a try
block we perform some operation that might fail and throws an exception. To prevent the application from exiting with an unexpected error at this point, we can safe-guard the operation with a try
block in order to handle the error appropriately. In theory, we can secure any operation in a try
block. However, in practice we often work with some resources that have to be treated at the end of processing, like closing input streams or files we accessed. In a catch
block we catch exceptions that were thrown in the try
block and handle them appropriately. And lastly, in an optional finally
block we do some cleanup work, like closing the resource we accessed in the try
block. Pretty simple so far.
However, in the reactive world we cannot just throw an exception in case of a failure, catch it somewhere and maybe clean up afterwards, whether the execution failed or not. Any data and so any exceptions must follow the downstream processing in the reactive chain. Breaking out of the reactive chain means breaking out of any benefit that Reactive Programming delivers. Therefore, it is important to be aware of the corresponding representation of errors and patterns to handle them in the reactive world.
I will focus on the functionality provided by Mono.using()
from the framework Project Reactor. Therefore, let’s have a look into the corresponding JavaDoc:
Uses a resource, generated by a supplier for each individual Subscriber, while streaming the value from a Mono derived from the same resource and makes sure the resource is released if the sequence terminates or the Subscriber cancels.
The attentive reader may have realised that these are more or less the individual steps of a try-catch-finally (or actually a try-with-resources), only transferred to the reactive world.
A supplier generates a resource and some processing is performed on this resource by applying a supplier function ( → try). This resource is then cleaned up by a consumer function for the initial resource ( → finally). The only thing we don’t find here right away is catching exceptions.
However, since exceptions are converted into error signals, we can also implement the “catch block” by reacting on those signals using appropriate methods in the reactive chain.
In a recent project, we wanted to load log files from a server and then process them. Later, those log files should be cleaned up, so that no file handles remain open and no temporary files are left on the file system. More precisely, we wanted to download execution logs from an Ansible Tower instance for our executed Ansible jobs. In this context, I had the opportunity to use the reactive “try-catch-finally” pattern. The workflow involves the following steps:
- First, a temporary file is created. This is our resource.
- Then, we download the logs from Ansible Tower, write it into the temporary file, and process the result.
- At the end, we want to delete the temporary file in any case.
- If anything unexpected happens, we want to handle the exception and rethrow it as an exception of our own.
This can be implemented as follows:
The whole part from line 2 to 23 is the “try-finally”.
Let’s take a closer look at the individual components of the “generateJobLog” method, beginning with the resource supplier:
First, the temporary file is generated by passing a resource supplier function to the method. When this call succeeded, the result will be passed to the source supplier function.
The call to the non-blocking WebClient
delivers us a stream of DataBuffers
, Spring’s abstraction over byte buffers. The log file is downloaded chunk by chunk, so that the memory pressure is minimized. However, now we end up with a Flux<DataBuffer>
, which we need to handle for further processing.
DataBufferUtils
provide some really convenient utility methods to handle the data buffers without hassle. We use DataBufferUtils.write
here, which takes a Flux of data buffers and chunk-wise writes it to the given file. Neat!
By flat-mapping the result of the DataBufferUtils
operation, we can be sure the file exists and contains the job log. Now, we can process the log data.
After handling the data chunks and further processing the populated data, the resource is passed to the resource cleanup consumer, which deletes the file if it exists.
Any occurring exception in the Mono.using
part will be properly propagated as error signal down the reactive chain. Therefore, the “catch” part of the “try-catch-finally” is emulated by using onErrorMap
.
In this variant, onErrorMap
takes a function that consumes a thrown exception and maps it to some other exception of choice.
There are plenty of other error handling methods available. Feel free to browse through the JavaDoc for Mono or Flux.
For comparison, I wanted to show you the implementation with the blocking RestTemplate
(instead of WebClient
) and via “try-catch-finally” as well:
The structure is roughly the same. We will need two try blocks to ensure cleaning up the temporary file at the very end of processing. Instead of Flux or data buffers, we work with the response from the RestTemplate
directly by defining a ResponseExtractor
in line 11. But beyond that, the differences are relatively small. Except, of course, that the reactive variant is non-blocking and this variant here is blocking.
As you have seen, dealing with resources in a reactive environment is not that difficult. Just try it out for yourself!
Thanks for reading! Feel free to comment or message me, when you have questions or suggestions. You might be interested in the other posts published in the Digital Frontiers blog, announced on our Twitter account.