Packaging Plans July 2025 #2211
Replies: 5 comments 19 replies
-
To get a rough idea of what people think of this, please upvote this if you think Rx v7 should go straight to introducing a new main package, relegating This is as close to a 'clean break' as it gets. |
Beta Was this translation helpful? Give feedback.
-
...and please upvote this if you think we should retain NOTE: when I originally wrote this, I described it as "the main facade in Rx 7." That was a typo. I mean "main package in Rx 7." The whole point of this design option is that |
Beta Was this translation helpful? Give feedback.
-
...and if you think there are other options that are better that are not listed above, please upvote this one. |
Beta Was this translation helpful? Give feedback.
-
Do all RX extension methods cause the issue, or only the ones that involve UI-specific code? If the latter, how many of those exist besides ObserveOn? Technically, a 'clean break' could force a rename of said extension methods as another option - albeit rough and dirty... |
Beta Was this translation helpful? Give feedback.
-
I'm tagging people who contributed to the original #2038 thread and who have not yet said anything here because I'm guessing at least some of you will have opinions, but might not have spotted this new discussion: @anaisbetts @akarnokd @dameng324 @ChrisPulman @Trinitek @wmanning @frankhaugen @matt-goldman @JakenVeina @muhamedkarajic @MauNguyenVan @heronbpv |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Our previous announcement that we were going to be moving forward with a new packaging plan for Rx.NET turned out to be premature. The plan, whose main goal is to fix the 'deployment bloat' issues that have led to some projects to abandon Rx, was predicated on the availability of a workaround for this problem, and as @kmgallahan pointed out, that workaround turned out not to work in some important cases. So we needed a less conservative plan.
Update I've recorded a video covering this update: https://www.youtube.com/watch?v=GSDspWHo0bo
Executive Summary
We have two main candidates for how to fix the current packaging problems. One retains
System.Reactive
as the main Rx package, and the other involves turning that into a legacy facade, and introducing a new main package, possibly calledSystem.Reactive.Net
.We have introduced a new testing approach to validate possible designs, and it provides evidence showing that both of these seem to solve the problems at hand. Both have significant problems.
The main problem with turning
System.Reactive
into a legacy facade is that if applications end up with a reference to the old (non-facade)System.Reactive
v6 and also toSystem.Reactive.Net
v7, then there will be two complete implementations of Rx.NET in scope simultaneously. Any time you try to use one of Rx's extension methods, the compiler will complain that the code is ambiguous—it won't know which of the two implementations you want. (This is actually very easy to fix: just add a reference toSystem.Reactive
v7, and its type forwarders effectively unify you back to a single implementation. But discovering this simple fix might be tricky for app authors. Even if we added a code analyzer inSystem.Reactive.Net
to detect this situation and tell you exactly how to fix it, it is certainly disruptive.)So retaining
System.Reactive
as the main package is attractive. However, there's one major problem: to do so, we have to use a 'clever' trick involving reference assemblies. 'Clever' tricks have a way of coming back to haunt us—this seems like exactly the sort of thing that might lead to another set of revisions to the packaging designs in a few years once all the unforeseen consequences of this approach become apparent. So we are very reluctant to employ such a trick in the main Rx assembly.For that reason, we believe that relegating
System.Reactive
to being a legacy facade is the safer of these two options.However, since the fears about the no-facade option are hypothetical, we are considering using that approach in Rx v7, because as far as we can tell, we would always be able to go to the safer design in Rx v8. (The no-facade design doesn't appear to close any doors. But turning
System.Reactive
into a legacy facade is a one-way trip.)What We've Done
To try to avoid another premature declaration of victory, we've changed a couple of things about our approach:
Note: the Rx.NET package feed is open for anonymous access, but you might find you get an access denied error when you try to access it. This typically happens if you're already logged into Azure DevOps. If you're logged in, Azure DevOps tries to show you the full version of the feed page that project maintainers get to see, but then it realises you don't have access to that. For some reason, instead of just falling back to the version that's visible to anonymous users, it shows you an access denied error instead. If that happens, try opening a private browsing session—as long as Azure DevOps doesn't think it knows who you are, it will just show you the for-public-consumption version of the page.
'Rx Gauntlet' test suite
Although Rx.NET has long had a very comprehensive unit test suite, unit tests can't check for many of the packaging-related problems we've had in the past, and might have in the future, hence the need for this new kind of test suite. For this reason, we've added Rx Gauntlet.
This is currently visible only on the feature/rx-gauntlet branch, on which it can be found at https://github.com/dotnet/reactive/tree/feature/rx-gauntlet/Rx.NET/Test/Gauntlet
The key difference between this and more conventional tests it that this is able to create projects and build NuGet packages dynamically, and set up local NuGet package feeds. This enables automated testing of a wide range of build and deployment scenarios, including many subtle variations on situations where applications end up with indirect references to Rx.NET, possibly with a mix of version numbers, and in a way that application authors may not be able to control.
This is important because many of the problems with potential Rx.NET packaging design candidates is that they only fail in quite specific scenarios, and without testing all of them, it is impossible to be confident that certain kinds of problems have truly been eliminated.
Currently, the goal is to use Rx Gauntlet to test potential designs, so that we can be confident in whatever choice we ultimately make. In the longer term, Gauntlet's goal will be to avoid regressions of a kind that unit tests can't catch.
Packaging design option prototypes
Currently we've got four prototypes:
7.0.0-preview-legacyfacade.1.ga1159cd7f3
System.Reactive.Net
main package, newSystem.Reactive.For.Wpf/WindowsForms/Uwp
packages;System.Reactive
becomes a legacy facade consisting of type forwarders to these new components7.0.0-preview-legacyfacade-refnoui.3.g7492bd514e
System.Reactive
, enabling that component to drop its transitive dependency on theMicrosoft.Desktop.App
framework7.0.0-preview-legacyfacade-refnoui-withfxref.1.g307e3b7f27
Microsoft.Desktop.App
framework still being present. This has better backwards compatibility, but you get bloat by default in scenarios where transitive dependencies mean you can't get rid of the legacySystem.Reactive
facade dependency. However, unlikeSystem.Reactive
v6, you can now prevent this with the<DisableTransitiveFrameworkReferences>
.7.0.0-preview-nofacade-refnoui.5.gc59ebd3e22
System.Reactive
remains as the main package. It does not force a dependency on theMicrosoft.Desktop.App
. To make that work it uses the same reference assembly trick as the preceding two options.To use these, you'll need a
NuGetConfig.xml
file pointing to the Rx.NET preview package feeds, e.g.:We are open to adding more if people have new suggestions, or if they believe that suggestions we have already rejected should not have been. (E.g., there still seem to be some believers in the clean break, who do not seem to be convinced by arguments that this approach doesn't solve the problems at hand, and also creates new ones; so perhaps we will need to go actually prototype this option, so that the evidence that it doesn't work will be clear and incontrovertible.)
Analysis of test results
Rx Gauntlet produces large volumes of JSON summarizing its output, and it can be tricky to find the interesting results. We've therefore applied some processing to this.
We've created a PowerBI report that analyzes three of the four test types, and shows summarized results. The results of that are in RxGauntletResults.pdf.
The fourth category, transitive dependencies, is more complex. We have a Python notebook analyzing that. This PDF summarises its output: Packaging Options for Rx Notes.pdf
Our current position
Currently, we are trying to decide between two preferred options. One is to introduce a new main Rx package, relegating
System.Reactive
to being a facade, and the other is to modifySystem.Reactive
but retain it as the main package.System.Reactive
as facade.This is prototyped in the
packaging-systemreactive-as-facade-ref-no-ui
branch. In this, we:System.Reactive.Net
, but that could change if necessary)System.Reactive.For.Wpf
System.Reactive
to a type forwarding legacy compatibility assembly, but using the reference assembly trick to make it possible for applications that can't avoid a dependency on this to escape from bloat by specifyingDisableTransitiveFrameworkReferences
; the trick we're using avoids the extension method problems that @kmgallahan identifiedHowever, there are two major disadvantages:
System.Reactive.Net
v7, butSystem.Reactive
v6); it is easily resolved by upgrading theSystem.Reactive
reference to v7, but it won't be obvious to people that this is what they need to do (unless we write a code analyzer to detect this problem and make the suggestion)These are strong arguments in favour of the
packaging-no-facade-ref-no-ui
design.Retain
System.Reactive
as main package.The
packaging-no-facade-ref-no-ui
design is the less disruptive option. So why isn't it a slam dunk? It has two problems:System.Reactive
continues to be the main Rx assembly, we will be doomed to have auap10.0.18362
(old pre-.NET 9 UWP) target in the main Rx assembly for the foreseeable future, quite possibly for as long as a decade, and that TFM causes enormous troubleThis second aspect is arguably not that important because the pain falls entirely on people working on Rx.NET. If that were the only issue, we'd probably steel ourselves and just accept that we're going to have to live with that pain.
The first one is the major concern. The history of Rx's attempts to solve packaging issues with 'clever' unusual solutions is sobering. This has the potential to be the latest in a line of things that future Rx.NET maintainers wish had never happened.
Introducing a new main Rx package is the closest thing we have to a 'clean break' that can actually work, and it presents an opportunity to make the core Rx package normal: no clever tricks, no workaround, no weird off-label practices. This seems likely to maximise our changes of finally seeing an end to weird and hard to resolve problems.
However, while we believe that a new Rx package could well be the best long-term design overall, we recognize that there are significant benefits to retaining
System.Reactive
as the main Rx assembly, if that can be made to work. So...Could We Try The No-Facade Approach First?
One possibility we are considering is trying the no-facade approach first despite some misgivings with it, and keeping the 'new main package' option as a fallback.
The idea here is that if we were to go ahead with the approach in which
System.Reactive
remains as the main package, and the 'clever' reference assembly trick turns out to cause problems, nothing stops us from turning it into a legacy facade in Rx 8 if we have to. This would make the Rx 7 release much less disruptive, and if our fears around the consequences of the trick turn out to be unfounded, then we've avoided an unnecessary introduction of yet another main Rx package.The idea is that the two main candidates are non-commutative: it is possible to try the 'no-facade' approach first and then our preferred option second. But if we go straight for our preferred option, we can't go back to the no-facade approach later.
The main risk here would be that we've missed something: perhaps publishing the no-facade design as v7 ties our hands in some way that makes changing
System.Reactive
into a facade in v8 (should that prove necessary) harder.Beta Was this translation helpful? Give feedback.
All reactions