ReactiveX / RxJava

RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
Apache License 2.0
47.84k stars 7.61k forks source link

3.x: Breaking API changes #5622

Closed akarnokd closed 5 years ago

akarnokd commented 6 years ago

This issue collects the API changes proposed for 3.x: unused operators and overloads, mistakes in signatures, etc.

ZacSweers commented 6 years ago

I'd like to propose making takeUntil and similar variants take a Maybe. The contract is that it takes until the first emission, which is the same contract as a Maybe.

JakeWharton commented 6 years ago

But why? A Single seems more appropriate contract-wise, but at that point I'd generalize it back to Observable which already exists. The need for those overloads is actually a need for polymorphic stream types instead.

On Tue, Oct 3, 2017 at 5:30 AM Zac Sweers notifications@github.com wrote:

I'd like to propose making takeUntil and similar variants take a Maybe. The contract is that it takes until the first emission, which is the same contract as a Maybe.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/ReactiveX/RxJava/issues/5622#issuecomment-333789357, or mute the thread https://github.com/notifications/unsubscribe-auth/AAEEERrIqc6C7UuvAoQI57Iap-Evhe9xks5sof67gaJpZM4Pj9aM .

ZacSweers commented 6 years ago

I guess it depends. Sometimes you takeUntil() something that may or may not actually emit, like many lifecycle handling patterns do

JakeWharton commented 6 years ago

But single isn't required to emit.

On Tue, Oct 3, 2017 at 5:15 PM Zac Sweers notifications@github.com wrote:

I guess it depends. Sometimes you takeUntil() something that may or may not actually emit, like many lifecycle handling patterns do

— You are receiving this because you commented.

Reply to this email directly, view it on GitHub https://github.com/ReactiveX/RxJava/issues/5622#issuecomment-333980979, or mute the thread https://github.com/notifications/unsubscribe-auth/AAEEEa77DEhI-gYqHRXDoLsH2myadDpHks5soqP9gaJpZM4Pj9aM .

ZacSweers commented 6 years ago

I suppose this is true, if the source stream completes it just disposes the single?

artem-zinnatullin commented 6 years ago

If we decide to keep target version #5620 lower than Java 8 then:

else

Sounds good?

akarnokd commented 6 years ago

@artem-zinnatullin Why? Adding the throws Exception was well worth the effort. In fact, the idea was to make it throws Throwable in v3. Btw, the topic warrants its own issue/discussion.

artem-zinnatullin commented 6 years ago

Ah damn checked exceptions. @akarnokd, you're right of course.

For rx chains it's good that Function declares exception and probably should declare Throwable, the use case I was dealing with today is when in our Java code we needed to call function.apply() and had to wrap it in a try-catch. Kotlin 😿

akarnokd commented 6 years ago
JakeWharton commented 6 years ago

Isn't that just a new overload and deprecation that can be done as part of 2.x?

akarnokd commented 6 years ago

Well, the alias could be added to 2.x but then toCompletable should not be in 3.x. Should we start aliasing and deprecating these types of methods in v2 so that users get better prepared for changes in 3.x?

JakeWharton commented 6 years ago

I think the general assumption is that everything which is deprecated will be removed in the next major version, yes. Plus it delays the need to even require that a 3.x exist.

davidmoten commented 6 years ago

In 3.x I'd like to make upstream work more precise (across networks for instance) as discussed in #5077, and do it without adding extra operators. For example, the first operator requests Long.MAX_VALUE of upstream as a perf optimisation that I reckon we can do without.

akarnokd commented 6 years ago
cerisier commented 6 years ago

Will .getValue() stay while .getValues() is removed ? Or do you plan to remove the ability to get any value(s) out of a Subject / Processor.

akarnokd commented 6 years ago

@cerisier getValue() methods are supposed to be already present only on the relevant types.

CoderSpinoza commented 5 years ago

Will 3.x have different package name from 2.x?

When I follow the package naming history of RxJava 2 in #3173, it seems that the final version of package name has changed to io.reactivex from 1.x's rx, but not to include version number eventually.

As described by Jake Wharton in one of the comments in the above issue, versioned package were somewhat of a rarity back in Aug 2015 when the discussion was active, but I believe some of the popular libraries have adopted the policy, mainly to support co-existence of different incompatible versions of libraries. Retrofit and OkHttp did for 2.0 and 3.0, respectively, as described in https://jakewharton.com/java-interoperability-policy-for-major-version-updates/.

Probably it's too early to ask this question, but I noticed that 3.0 milestone is due by end of 2019 and so many RxJava consumers who are library authors will want to know the direction. Please notify me if this belongs to a separate issue, and I will post a new issue. Thanks in advance :)

akarnokd commented 5 years ago

2.x was had the complete architecture remade so it warranted a separate package. 3.x will be very likely mostly API breaking changes but the package will remain io.reactivex and users switching to it may only have to adjust to the API changes with certain operators.

JakeWharton commented 5 years ago

That sounds like a really bad idea!

akarnokd commented 5 years ago

I don't see any reason to have 2.x and 3.x live side-by-side. The top reason for 3.x to exist is to fix the API mistakes.

JakeWharton commented 5 years ago

It makes practical migration impossible for large-scale projects using libraries built on Rx and discourages its use in the future.

CoderSpinoza commented 5 years ago

Consumers do not voluntarily let 2.x and 3.x live side-by-side. It will be due to incompatible versions of a common library (such as RxJava, networking, or json parsing library) used by both libraries and consumers.

Renaming packages when updating major version makes migration period really smooth and incremental with least amount of headache for both. If this isn't the case, the only choice left to library authors will be to use no library at all and writing everything from scratch, which they really don't want to.

akarnokd commented 5 years ago

There is one way to find out: make the breaking changes and see how much code breaks in the popular libraries. Unlike RxJava 2, RxJava 3 would be a drop-in replacement to RxJava 2, minus a few API adjustments.

JakeWharton commented 5 years ago

That is quite a user-hostile approach.

Also "drop-in replacement" and "a few API adjustments" are not compatible. It's either a drop-in replacement that's binary compatible OR it requires API adjustments requiring all libraries update before you can.

akarnokd commented 5 years ago

How many libraries are using the operators in this list? Why are they using blockingGet in non-test code, for example? Do you replace a dependent libraries without recompiling/retesting the host library/application?

I'm almost certain RxAndroid, RxRelay, RxBinding, RxSharedPreferences either won't have to change or would need a simple recompile if the Rx functional interfaces get widened to throws Throwable. There won't be any architectural change with 3.x, create and X.subscribeActual will remain the same, as will fromCallable and the Scheduler/Worker API. JDK support will also remain 6.

So I don't believe the set of breaking changes proposed will do more than just a small inconvenience. I'll personally take the time and post PRs to any open-source project that depended on the old signatures and update them to version 3 usage.

Consumers do not voluntarily let 2.x and 3.x live side-by-side. It will be due to incompatible versions of a common library (such as RxJava, networking, or json parsing library) used by both libraries and consumers.

Do you think 3rd party library authors are willing to support 2 versions, one for 2.x and one for 3.x, at the same time for an overlapping duration? What if the signature changes do not affect them at all?

Let's turn it around. Why do you think I have the time or will to support two versions, again? Why would I have to spend time answering SO questions/issue questions about why Schedulers.io() no longer compiles because the OP forgot to change the import to like io.reactivex.rxjava3?

RxJava's API has to improve, as the minor inconveniences turn into major ones over time. Also I'm pretty positive about the ecosystem's flexibility; they will move forward as well, sooner or later.

All this said, here is the deal:

  1. Fix up the API and release RxJava 3 with the same package structure but updated maven address io.reactivex.rxjava3.
  2. Stop adding new features to RxJava 2, only bugfixes and significant JavaDocs updates.
  3. Keep supporting RxJava 2 this way for 1.5 years tops.
  4. Retire RxJava 2.
ZacSweers commented 5 years ago

Keeping the same package structure with different maven coordinates will cause classpath collisions right?

On Tue, Feb 19, 2019 at 2:13 PM David Karnok notifications@github.com wrote:

How many libraries are using the operators in this list? Why are they using blockingGet in non-test code, for example? Do you replace a dependent libraries without recompiling/retesting the host library/application?

I'm almost certain RxAndroid, RxRelay, RxBinding, RxSharedPreferences either won't have to change or would need a simple recompile if the Rx functional interfaces get widened to throws Throwable. There won't be any architectural change with 3.x, create and X.subscribeActual will remain the same, as will fromCallable and the Scheduler/Worker API. JDK support will also remain 6.

So I don't believe the set of breaking changes proposed will do more than just a small inconvenience. I'll personally take the time and post PRs to any open-source project that depended on the old signatures and update them to version 3 usage.

Consumers do not voluntarily let 2.x and 3.x live side-by-side. It will be due to incompatible versions of a common library (such as RxJava, networking, or json parsing library) used by both libraries and consumers.

Do you think 3rd party library authors are willing to support 2 versions, one for 2.x and one for 3.x, at the same time for an overlapping duration? What if the signature changes do not affect them at all?

Let's turn it around. Why do you think I have the time or will to support two versions, again? Why would I have to spend time answering SO questions/issue questions about why Schedulers.io() no longer compiles because the OP forgot to change the import to like io.reactivex.rxjava3?

RxJava's API has to improve, as the minor inconveniences turn into major ones over time. Also I'm pretty positive about the ecosystem's flexibility; they will move forward as well, sooner or later.

All this said, here is the deal:

  1. Fix up the API and release RxJava 3 with the same package structure but updated maven address io.reactivex.rxjava3.
  2. Stop adding new features to RxJava 2, only bugfixes and significant JavaDocs updates.
  3. Keep supporting RxJava 2 this way for 1.5 years tops.
  4. Retire RxJava 2.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/ReactiveX/RxJava/issues/5622#issuecomment-465142759, or mute the thread https://github.com/notifications/unsubscribe-auth/ABTEvlMOKnk5nMgNpfhuP2wGPjEyXSlmks5vPAZwgaJpZM4Pj9aM .

akarnokd commented 5 years ago

exclude group: 'io.reactivex.rxjava2', module: 'rxjava' or don't upgrade until the dependent libraries upgrade? Let's turn it around: we release under the new package structure -> almost no libraries for months.

Not sure how other libraries match the releases of RxJava 2 at the moment; RxAndroid does not follow for sure. Mine do follow.

akarnokd commented 5 years ago

Also for the throws Throwable. According to the spec: they are checked at compile time only.

In addition, widening seems to be okay too for compile time:

public class ThrowsSubclass {

    interface F {
        void m() throws Throwable; // was throws Exception
    }

    public static void main(String[] args) {
        new F() {
            @Override
            public void m() throws Exception {

            }
        };
    }
}
JakeWharton commented 5 years ago

Libraries don't need to match the releases of RxJava and there's no reason to do this because of binary compatibility and dependency resolvers. If anything, libraries should be doing the opposite and expressing the minimum version they work with to not force behavior changes on their consumers unless desired.

To answer your specific questions...

How many libraries are using the operators in this list?

It's impossible to know, of course, but it's guaranteed to be more than zero.

Why are they using blockingGet in non-test code, for example?

This is a weird example considering it's the least-used and least-useful of the bunch. So while it might be zero, the others are non-zero.

Do you replace a dependent libraries without recompiling/retesting the host library/application?

Of course. Transitive dependency resolution guarantees this to happen with libraries. It's why we have jars and why Java does linking at runtime instead of statically at compile-time.

I'm almost certain RxAndroid, RxRelay, RxBinding, RxSharedPreferences either won't have to change or would need a simple recompile if the Rx functional interfaces get widened to throws Throwable.

While this represents a popular set of libraries for Android, in the scope of the RxJava ecosystem it's tiny. And this specific change is only source-incompatible, not binary-incompatible, so it's not as worrying as the API changes.

There won't be any architectural change with 3.x, create and X.subscribeActual will remain the same, as will fromCallable and the Scheduler/Worker API. JDK support will also remain 6.

This only reaffirms my belief that there's no need for a version 3 right now. A useful RxJava 3 to me should be built against j.u.c.Flow and architected in a way to take advantage of Project Loom when it arrives.

Do you think 3rd party library authors are willing to support 2 versions, one for 2.x and one for 3.x, at the same time for an overlapping duration? What if the signature changes do not affect them at all?

Some did this for 1.x and 2.x, yes. But the interop library actually means you're not even required to do it. The consumer of libraries could upgrade piecemeal by slowing moving the interop barrier across their application until the need for it was removed.

Why do you think I have the time or will to support two versions, again?

I don't even want an RxJava 3 so I'm not asking you to. But beyond that, there's plenty of people around this project that can and will help.

Later on in the post your plan outlines that you're already going to support both though so based on that you seem to think you have the time.

Why would I have to spend time answering SO questions/issue questions about why Schedulers.io() no longer compiles because the OP forgot to change the import to like io.reactivex.rxjava3?

Are these going to number greater or less than the NoSuchMethodError posts? And the answer easier or harder to explain? But, again, there's a whole community that can help do this and it's the point of StackOverflow's whole system.

RxJava's API has to improve

Sure, but it needs some carrot balanced with the stick.

Right now the value proposition of 3.x is practically non-existent for consumers. We get a few warts fixed, sure, but at a cost that disproportional to the benefit.

as the minor inconveniences turn into major ones over time.

On a long enough timeline they might, but are these really at that point? None of the proposed changes unlock some fundamental potential that's missing.

Also I'm pretty positive about the ecosystem's flexibility; they will move forward as well, sooner or later.

I am too. Except actions like this will actually force a subset to alternate solutions which aren't hostile to their builds which is unfortunate. Or they'll simply stay on 2.x.

we release under the new package structure -> almost no libraries for months.

Does this matter? It doesn't affect this library. At least in this case when consumers want to upgrade they can do so incrementally without a flag day where everything needs to change at once.

I want to see a 3.x that moves the goal posts for what a reactive library can be and makes Reactor look obsolete. Not something that fixes a few dings in the paint of its bumper.

akarnokd commented 5 years ago

@JakeWharton I understand your arguments, but as you put it "Does this matter? It doesn't affect this library". So if 3.x is created, how does it affect existing libraries and their maintainers?

I'm trying to understand the underlying concerns here. If nobody supports it -> nobody upgrades -> 3.x dies out. If some high-profile projects support it -> push others to upgrade -> 3.x thrives eventually. Every supplier upgrades -> clients upgrade -> 3.x thrives. Loom/coroutines win the market -> mostly nobody cares about RxJava ever again. Either way, creating 3.x does not delete 2.x and all those depending on 2.x.

Are you worried about split community? Expect constant nag on projects to upgrade to RxJava 3? Are you worried about RxJava 2 support drying out?

I want to see a 3.x that moves the goal posts for what a reactive library can be and makes Reactor look obsolete.

Reactor is targeting the desktop/server/cloud world and as such, it can always be on the edge of the tech curve. Also let me state that RxJava did not and does not compete with Reactor.

We are held back by Android's Java 6 baseline. I'd be quite happy if that would bump to Java 9 where I'm more willing to repackage RxJava - a very requirement for modules to work properly anyway as we'd have to move code from io.reactivex to io.reactivex.publishers or something due to how modules must be organized. At that point everything already broke so everybody will suffer the same.

JakeWharton commented 5 years ago

Is RxJava held back by that? The Android community had to fight for 2.x to support Java 6 instead of being Java 8+. I'm prepared to argue for 3.x to leave Android far, far behind. We can take care of ourselves by maintaining 2.x indefinitely. Most Java libraries should start to consider abandoning Android as the Java and JVM ecosystem really kicks into high gear and Android shows no sign of keeping pace at any reasonable timeline based on what's happening (or not) in AOSP.

akarnokd commented 5 years ago

The Android community had to fight for 2.x to support Java 6 instead of being Java 8+.

I remember it differently: #3450. I don't see any hard fights there, but correct me if I, or the other maintainers at the time, happen to have resisted somewhere else regarding the support level. I even put in the extra effort and made RxJava 2 Java 6 compatible again, undoing some of my own Java 8 related work in the process.

Android shows no sign of keeping pace at any reasonable timeline

Kotlin has extension methods which makes the need for breaking changes obsolete there, hence 3.x is unnecessary. Just add a new extension method with the correct return type and hide the "bad" methods. Also Kotlin coroutines make one-shot async processing convenient enough most of the time that RxJava is abandoned en-masse already. They don't care what 3.x breaks.

eygraber commented 5 years ago

As an Android developer, I'd be fine with sticking on RxJava2 until there was an actual need for a major version bump. And neither I, nor anyone I know have abandoned RxJava for coroutines when the use case calls for it, which is often. Some of us are quite bought-in to rx.

I really wouldn't mind seeing RxJava (or a reactive implementation) built on coroutines but that's more for ease of implementing operators (which isn't so bad with Kotlin because of extensions). I guess it would just be cool.

davidmoten commented 5 years ago

For 3.x I think it would be nice to see PublishSubject.create() return what is the 2.x equivalent of PublishSubject.create().toSerialized(). Unserialized access of PublishSubjects seems to turn up a lot as the cause of people's problems. An additional factory method for the performant but serialize access yourself version could be added - e.g. PublishSubject.createRequiresSerialization().

akarnokd commented 5 years ago

Closing via #6514, #6516 and #6517.