More that year ago I've started work on Reactive Toolbox. It still more a research than the something practically usable. Nevertheless, recent progress in the implementation gives me hope that there is a solution for many long lasting problems in Java development.
Whole last decade for Java development is a strange time.
On one hand many exciting things happened to Java. A lot of exceptionally useful and really progressive features were added. These features allowed to write very modern, fast and reliable code.
On the other hard, everyday Java development experienced quite small changes. We're still using slow and bloated Spring, just switched from XML to annotations. We're still fighting with NPE's, using exceptions as part of the business logic and writing AOP expressions inside text constants. We all know that this is bad practices, after all great "Effective Java" by Joshua Bloch is the book known to every Java programmer. We still delaying switching to more recent Java versions because our frameworks and libraries don't play well with them. We mixing and matching Java frameworks and libraries to create our apps and while this allows us to create apps fast enough, in most cases we barely know what's happening under the hood. For example, do you know how many threads started when you launch your Spring Boot app?
It's hard to say that we're not aware about how to write code differently. The number of publications about alternative approaches is just overwhelming (for example, take look at John McClean blog or Pierre-Yves Saumont blog or Neal Ford series of articles.
So, what's the problem, why we're still using inefficient stone age frameworks and approaches?
I think there are several factors.
First of all, of course, the inertia. Every Java book, every Java course teaches how to write traditional imperative Java code. Sometimes inertia even encoded in "best practices" and Sonar rules, like "do not use Optional for fields or parameters".
Second one is the fear to not be in trend. Younger Java devs especially often feel it. And since from every single hole they hear Spring noise, they believe that there is no other way to write Java "cloud native" (what the hell this does mean?) software. After all "nobody was fired for choosing Spring".
If you see that I mention Spring too often that's because I believe it is responsible for creating Java reputation of slow and bloated language which requires enormous amounts of memory.
All this made me sad, as I know that Java is exceptionally efficient, fast and expressive language. At some point I've decided that I need to do something with this and started collecting techniques and approaches which allow conveniently write modern and reliable Java code.
Of course, Reactive Toolbox is not the first attempt to change way we're writing Java code. Used techniques and approaches are also not new - mostly they are based on well known Functional Programming (referred below as FP for short) ideas. But there are several things which I believe make Reactive Toolbox (below RT for short) different:
- RT is very pragmatic and focused on practical use. It does not bring a lot of FP "slang" nor tries to force user to learn FP or Category Theory concepts and ideas first.
- RT makes no compromises with existing code, even Java Standard Runtime Library. Code which does not play well with RT approaches is replaces or wrapped. Unfortunately there is too much such code, so there is a very long path ahead. Fortunately this gives unique chance to abandon legacy API's and finally stop using them - thing which we should've been done decade ago.
- RT is Reactive Toolbox: asynchronous and parallel code execution along with high performance asynchronous I/O is at very heart of the framework.
Finally RT uses a convention which simplifies handling of
- All values which should not be
nullrepresented with plain types.
- All values which can be missing represented with types wrapped into
Option. This convention is applied everywhere, so
Optionmay be present in method parameters and class fields. Note that this contradicts "best practices" which limit use of
Optionalto return values only and therefore completely eliminate all benefits provided by this gem residing in Standard Java Library since Java 8.
Let's look deeper: Pragmatic Functional Java
As mentioned above, RT is focused on practical use rather than theoretical purity or agreement with traditional FP slang or approaches.
Reactive Toolbox API completely eliminates exceptions and uses dedicated type -
Result<T> - to handle and propagate information about errors. The approach is described here in more details.
Pragmatic approach here shows up in the use of specialized type
Result<T> type instead of more general
Either<F,T>, trading generality for brevity and readability.
Similar approach is taken with basic asynchronous primitive -
Promise<T>. It has built-in error handling (with the same
Result<T> type) instead of being a general purpose container. Again, it trades generality for brevity and readability. And even more for convenience.
Being mixed up together these techniques allow to write very concise and expressive code. Along with most recent versions of Java (at the moment of writing, Java 14 with preview features enabled) the resulting code barely longer that similar code written in Kotlin, debunking myth of Java verbosity.
Let's look deeper: Asynchronous Processing and I/O
The Promises in RT is a core primitive to work with asynchronous tasks and perform asynchronous I/O. Access to task scheduler and low level I/O operations provided as part of the Promise API.
The implementation of the asynchronous I/O in RT is based on the most advanced asynchronous I/O API present in recent versions of Linux kernels io_uring.
This serves several purposes at once:
- Vast majority of applications don't need any other executor than one provided by RT. No manual setup/configuration/control/synchronization/etc. is necessary.
- Single consistent low level API for all I/O operations, from opening file or socket to read/write/stat/copy and close.
- Promise-bases asynchronous execution requires no synchronization at application level. Just write code as for single thread application!
Drawbacks of The Taken Approach
As mentioned above, RT makes no compromises in regard to compatibility of existing code (including standard library) with RT model. This makes huge amount of existing code useless and often requires wrapping or rewriting from scratch even some rather basic things like parsing numbers from string.
And the main limitation as for now: as mentioned above RT uses API which is available in most recent Linux kernels. This means that RT does not work on Windows or MacOS, as well as Linux kernels before 5.6. As of time of writing these kernels are not part of standard distributions yet and in order to even experiment with RT one will need to install new kernel.
Top comments (3)
Thanks for sharing! Promises, async code and single-threaded executors look like pre-coroutines JS, with callbacks trees. What do you think about its readability? Is code using RT as maintainable as traditional threaded?
Well, built in executor is multithreaded and code written with Promises is not a traditional callback-based. Here and here you can find more details.
As for readability, you can take a look at some examples here.
As for maintainability (a little long :) ).
The RT is a research project and the goal of research is to define new coding style (I call it "Pragmatic Functional Java") which would enable writing readable, maintainable and reliable code with low mental overhead. One of specifics of this style is heavy use of monads and Promise is one of them. Monads allow abstracting out significant amount of run-time details, in particular underlying concurrency model. In other words, there is no visible difference between single-threaded vs multi-threaded code. As a consequence, this code is proof to issues of traditional multithreaded code and therefore is more maintainable.
That was Enlightenment. You've pushed me so far to start looking java again after years of deep dive into Nodejs. Thanks for sharing.