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 Ian has recorded a video covering this update: https://www.youtube.com/watch?v=GSDspWHo0bo
Executive SummaryWe 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 called System.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 to System.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 to System.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 in System.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.)
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 suiteAlthough 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 prototypesCurrently we've got four prototypes:
Preview Tag Branch Description7.0.0-preview-legacyfacade.1.ga1159cd7f3
feature/packaging-systemreactive-as-facade New System.Reactive.Net
main package, new System.Reactive.For.Wpf/WindowsForms/Uwp
packages; System.Reactive
becomes a legacy facade consisting of type forwarders to these new components 7.0.0-preview-legacyfacade-refnoui.3.g7492bd514e
feature/packaging-systemreactive-as-facade-ref-no-ui As above, but using a trick with reference assemblies to hide the UI-framework-specific types from the compiler in System.Reactive
, enabling that component to drop its transitive dependency on theMicrosoft.Desktop.App
framework 7.0.0-preview-legacyfacade-refnoui-withfxref.1.g307e3b7f27
feature/packaging-systemreactive-as-facade-ref-no-ui-with-fxref As above, but with the transitive dependency on the 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 legacy System.Reactive
facade dependency. However, unlike System.Reactive
v6, you can now prevent this with the <DisableTransitiveFrameworkReferences>
. 7.0.0-preview-nofacade-refnoui.5.gc59ebd3e22
feature/packaging-no-facade-ref-no-ui This does not introduce a new main Rx package. System.Reactive
remains as the main package. It does not force a dependency on the Microsoft.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.:
<?xml version="1.0" encoding="utf-8"?> <configuration> <packageSources> <add key="RxNet" value="https://pkgs.dev.azure.com/dotnet/Rx.NET/_packaging/RxNet/nuget/v3/index.json" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> </packageSources> </configuration>
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 resultsRx 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 positionCurrently, our preferred option is packaging-systemreactive-as-facade-ref-no-ui
. 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 specifying DisableTransitiveFrameworkReferences
; the trick we're using avoids the extension method problems that @kmgallahan identifiedHowever, there are two major disadvantages:
System.Reactive.Net
v7, but System.Reactive
v6); it is easily resolved by upgrading the System.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. So why don't we prefer that? It has two problems:
System.Reactive
continues to be the main Rx assembly, we will be doomed to have a uap10.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 this is likely to 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...
One possibility we are considering is trying the no-facade approach first despite our discomfort 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.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4