14

I am just getting started with RxJava, Java's implementation of ReactiveX (also known as Rx and Reactive Extensions). Something that really struck me was the massive size of RxJava's Flowable class: it has 460 methods!

To be fair:

  • There are a lot of methods that are overloaded, which bumps the total number of methods significantly.

  • Perhaps this class should be broken up, but my knowledge and understanding of RxJava is very limited. The folks who created RxJava are surely very smart, and they can presumably offer valid arguments for choosing to create Flowable with so many methods.

On the other hand:

  • RxJava is the Java implementation of Microsoft's Reactive Extensions, and that does not even have a Flowable class, so this is not a case of blindly porting an existing class and implementing it in Java.

  • [Update: The previous point in italics is factually incorrect: Microsoft's Observable class, which has over 400 methods, was used as the basis for RxJava's Observable class, and Flowable is similar to Observable but handles backpressure for large volumes of data. So the RxJava team were porting an existing class. This post should have been challenging the original design of the Observable class by Microsoft rather than RxJava's Flowable class.]

  • RxJava is only a little over 3 years old, so this is not an example of code being mis-designed due a lack of knowledge about good (SOLID) class design principles (as was the case with early releases of Java).

For a class as big as Flowable its design seems inherently wrong, but maybe not; one answer to this SE question What is the limit to the number of a class methods? suggested that the answer is "Have as many methods as you need".

Clearly there are some classes that legitimately need a fair number of methods to support them regardless of language, because they don't readily break down into anything smaller and they have a fair number of characteristics and attributes. For example: strings, colors, spreadsheet cells, database result sets and HTTP requests. Having perhaps a few dozen methods for classes to represent those things doesn't seem unreasonable.

But does Flowable truly need 460 methods, or is it so huge that it is necessarily an example of bad class design?

[To be clear: this question specifically relates to RxJava's Flowable class rather than God objects in general.]

  • Possible duplicate of How do I prove or disprove "God objects" are wrong? – gnat Jan 25 '18 at 7:54
  • 1
    @gnat Well certainly related, but it is not a duplicate. That question was generic, and my question specifically relates to RxJava's Flowable class. – skomisa Jan 25 '18 at 7:58
  • @skomisa Then fix the title to fit your question. – Euphoric Jan 25 '18 at 7:59
  • @Euphoric Point taken. – skomisa Jan 25 '18 at 8:00
  • 1
    This question is very interesting and founded. However, I'd suggest to reword it slightly in order to adopt a less subjective style (probably due to the initial shock ;-) ) – Christophe Jan 25 '18 at 8:28
14

TL;DL

The lack of language features of Java compared to C# as well as discoverability considerations made us put source and intermediate operators into large classes.

Design

The original Rx.NET was developed in C# 3.0 which has two crucial features: extension methods and partial classes. The former lets you define instance methods on other types which then appear to be part of that target type whereas partial classes allows you to split large classes into multiple files.

Neither of these features were or are present in Java, therefore, we had to find a way to have RxJava usable conveniently enough.

There are two kinds of operators in RxJava: source-like represented by static factory methods and intermediate-like represented by instance methods. The former could live in any class and thus they could have been distributed along multiple utility classes. The latter requires an instance to be worked on. In concept, these all could be expressed via static methods with the upstream as the first parameter.

In practice, however, having multiple entry-classes makes feature discovery by new users inconvenient (remember, RxJava had to bring a new concept and programming paradigm into Java) as well as usage of those intermediate operators somewhat of a nightmare. Therefore, the original team came up with the so-called fluent API design: one class that holds all the static and instance methods and represents a source or processing stage all by itself.

Due to the first class nature of errors, concurrency support and functional nature, one can come up with all sorts of sources and transformations regarding the reactive flow. As the library (and concept) evolved since the days of Rx.NET, more and more standard operators were added, which by nature increased the method count. This lead to two usual complaints:

  • Why are there so many methods?
  • Why isn't there a method X that solves my very particular problem?

Writing reactive operators is hard task not many people mastered over the years; most typical library users can't create operators by themselves (and often it is not really necessary for them to try). This means we, from time to time, add further operators to the standard set. In contrast, we have rejected many more operators due to being too specific or simply a convenience that can't pull its own weight.

I'd say the design of RxJava grew organically and not really along certain design principles such as SOLID. It is mostly driven by usage and feel of its fluent API.

Other relations to Rx.NET

I joined the RxJava development in late 2013. As far as I can tell, the initial early 0.x versions were largely a black-box reimplementation where the names and signatures of Rx.NET Observable operators as well as a few architectural decisions were reused. This involved about 20% of the Rx.NET operators. The main difficulty back then was the resolution of language and platform differences between C# and Java. With great effort, we managed to implement many operators without looking at the source code of Rx.NET and we ported the more complicated ones.

In this sense, up until RxJava 0.19, our Observable was equivalent to Rx.NET's IObservable and its companion Observable extension methods. However, the so-called backpressure problem appeared and RxJava 0.20 started to diverge from Rx.NET on a protocol and architectural level. The available operators were exteneded, many became backpressure-aware and we introduced new types: Single and Completable in the 1.x era, which have no counterparts in Rx.NET as of now.

Backpressure-awareness complicates things considerably and the 1.x Observable received it as an afterthought. We swore allegiance to binary compatibility so changing the protocol and API was mostly not possible.

There was another problem with Rx.NET's architecture: synchronous cancellation is not possible because to do that, one needs the Disposable to be returned before the operator starts executing. However, sources such as Range were eager and don't return until they finish. This problem can be solved by injecting a Disposable into the Observer instead of returning one from subscribe().

RxJava 2.x was redesigned and reimplemented from scratch along these lines. We have a separate, backpressure-aware type Flowable which offers the same set of operators as Observable. Observable does not support backpressure and is somewhat equivalent to Rx.NET's Observable. Internally, all reactive types inject their cancellation handle to their consumers, allowing synchronous cancellation to work efficiently.

10

While I'll admit I'm not familiar with the library, I took a look at the Flowable class in question and it seems like it's acting like a hub of sorts. In other words, it is a class meant to validate input and distribute calls accordingly in the project.

This class therefore would not really be considered a God object as a God object is one that attempts to do everything. This does very little in terms of logic. In terms of single-responsibility, the class's only job could be said to be delegating work throughout the library.

So naturally such a class would require a method for every possible task you'd require of a Flowable class in this context. You see the same type of pattern with jQuery library in javascript, where the variable $ has all the functions and variables necessary to perform calls on the library, albeit in the case of jQuery, the code is not simply delegated but also a good bit of logic is run within.

I think you should be careful to make a class like this, but it has its place so long as the developer remembers that it is only a hub, and therefore doesn't slowly convert to a god object.

  • 2
    Apologies for removing acceptance of your answer, which was both helpful and enlightening, but akarnokd's subsequent response was from someone on the RxJava team! – skomisa Jan 30 '18 at 22:25
  • @skomisa I could hope for nothing more if it were my own question! No offense taken! :) – Neil Jan 31 '18 at 7:12
7

.NET's RX's equivalent to Flowable is Observable. It also has all of those methods, but they are static and usable as extension methods. The main point of RX is that the composition is written using fluent interface.

But for Java to have fluent interface, it needs those metods to be instance methods, as static methods wouldn't compose nicely and it doesn't have extension methods to make static methods composable. So in practice, all of those methods could be made static methods, as long as you were fine not using fluent interface syntax.

  • 3
    The fluent interface concept is, IMHO, a workaround for language limitations. Effectively, you are building a language using a class on top of Java. If you think about how many features in even a simple programming language has and don't count all the overloaded variants, it becomes fairly easy to see how you end up with this many methods. The functional features of Java 8 can address a lot of the issues that lead to this kind of design and contemporary languages such as Kotlin are moving to be able to get the same kind of benefits without the need for method chaining. – JimmyJames Jan 25 '18 at 20:53
  • Thanks to your post I dug deeper and it seems that .NET's RX's Observable is ≈ to RxJava's Observable, so a premise of my question was incorrect. I have updated the OP accordingly. Also, RxJava's Flowable ≈ (Observable + a few extra methods for parallelizing and handling backpressure). – skomisa Jan 27 '18 at 8:37
  • @skomisa Java's Observable too has hundreds of methods. So you can compare the two. But the major difference is that .NET's Observable is static and all methods are static. While Java's isn't. And that is huge difference. But in practice, they behave the same. – Euphoric Jan 27 '18 at 9:12

Not the answer you're looking for? Browse other questions tagged or ask your own question.