Aspnetcore: Consider migration story for @aspnet/signalr

Created on 28 Jun 2019  Β·  17Comments  Β·  Source: dotnet/aspnetcore

TL;DR: If you develop a JavaScript library component that depends on @aspnet/signalr, we'd like to hear from you!

One open question that we'd like some support from the community on is how to manage the name transition. Here, we'd love feedback from app developers and anyone developing other NPM components that depend upon @aspnet/signalr.

My biggest concern is if a library component references @aspnet/signalr at version >=1.0.0. They are saying they work with versions 1.0 and higher, but the problem is that version 3.0 is not part of that set (because it's a different package name).

We have a few options

  1. Just stop shipping @aspnet/signalr.

    • Pros: It's easy

    • Cons: People using @aspnet/signalr don't know that we changed

  2. Ship one last version of @aspnet/signalr (probably version 3.0) and immediately deprecate it on NPM.

    • Pros: Still pretty easy, clearly tells users they need to update (via the deprecation message)

    • Cons: Libraries taking a dependency on @aspnet/signalr either have to update, or can't get the new stuff

  3. Ship @aspnet/signalr as a "shim package". This would be a package that has a direct dependency on the matching version of @microsoft/signalr and re-exports all the types. So it's content would be something like the snippets below

    • Pros: Nobody is broken

    • Cons:



      • We have to maintain this package for some undefined period of time.


      • If an app is already using the new version (@microsoft/signalr) and references a library using @aspnet/signalr version 1.x (BEFORE the shimming), then they may end up with two copies of the library. The 1.x version of @aspnet/signalr and the 3.x version of @microsoft/signalr. Thanks to JavaScript this will probably still work, types can be exchanged just fine.



I think if we did option 3, we'd also mark each release of these shims as deprecated so that NPM users get the warning. That means that if an app did use @microsoft/signalr and a library it depends on used @aspnet/signalr you would get a warning indicating you were in a slightly odd state. If you manually installed the shim package @aspnet/signalr it would fix the odd state since now the library would be using the shim and there'd only be one copy of @microsoft/signalr

@aspnet/signalr package.json

{
    "name": "@aspnet/signalr",
    "main": "index.js",
    "dependencies": {
        "@microsoft/signalr": "[exact same version]"
    }
}

@aspnet/signalr index.ts

export * from "@microsoft/signalr"
accepted area-signalr

Most helpful comment

Hey hi hello πŸ‘‹

I have some thoughts/opinions that I wanted to share:

I would not recommend doing any kind of mirroring of APIs. It creates a lot more work for a negligible amount of end-user benefit. The amount of work needed to set this up, maintain it, and – most importantly – fix it if anything goes wrong is a much higher cost than any possible end-user benefit that I am aware of. If you want to get into technical reasons, this is not a good path because it effectively gets around npm, yarn, pnpm and any other package manager's deduping of redundant modules which will end up increasing deployment/bundle sizes for end-users.

Having @microsoft/signalr have a direct dependency on @aspnet/signalr feels dishonest for lack of a better word.

I 100% agree. Being totally honest: if I'd seen this before I worked at Microsoft, my assumption would be that whoever did this was not a part of the JavaScript ecosystem and was instead just someone from .NET trying to make the JS folks happy while not really caring about them.

Regardless of the positive intent, it strays from how the JavaScript ecosystem works and comes off as uninformed/unaware – which is definitely not my experience with the SignalR team(s) so far now that I'm at Microsoft.

I'm also thinking of the community that requested we do this; the implication is folks didn't want to use it because of the implication that "ASP.NET is a requirement for SignalR."

Being totally unaware of SignalR before working at Microsoft, this wasn't quite my perception. The perception I had when learning about @aspnet/signalr was that SignalR was _only_ for ASP.NET – meaning I couldn't use it as a Node.js developer. But, I suppose that could technically also be perceived as ASP.NET being a req for SignalR usage.

As a Node.js ecosystem member, I'd like to make a suggestion – definitely weigh the pros/cons but IMO it makes the most sense _to me_:

Do option 2. Walking through _why_:

  1. No new version needs to be shipped – deprecation can be done directly without shipping a new version. See the docs on npm deprecate. This also doesn't need to be done _immediately_ if we feel it's too disruptive – we could deprecate in 365 days, or any other predefined time that we'd like to settle on.
  2. Deprecation is soft, not hard. If there are troubles for specific minor/patch versions we can un-deprecate those if absolutely needed. This should be a last resort that is taken very seriously.
  3. Since this is a semver major (two, actually?), most people should theoretically need to update anyway since npm's default is to gracefully allow for semver minor upgrades (see the caret version npm semver documentation, which is default npm behavior). As such, regardless of the scope people would have to be very intentional about upgrading their versions as a semver major version. The only downside here is that automated update tooling like Greenkeeper won't be able to auto-PR the semver major change.

Additionally, I'd like to share some reasons why I'd be _extremely hesitant_ to suggest Option 4:

  • You would need to recreate every published version of @aspnet/signalr. If there is _one single_ inconsistency in any/all of the re-publishes, you have 72 hours from publish to either delete it or re-publish because of npm's unpublish policy. This is something I would strongly recommend against.
  • The @aspnet/signalr modules will continue to work perpetually – they're not going anywhere, since they're immutable. As such, there's no real need for something like a migration path _for those older versions with the new namesapce_. If you're using those versions, you point to @aspnet/signalr. If you're using newer versions, you point to @microsoft/signalr. The only kind of migration that _should_ be happening is whatever semver major changes are introduced in the newer version – adding an additional change to a LOC that would have _already_ changed should be theoretically uncontroversial.

    • The caveat here is megacorps that have to approve every dependency manually. That said, my experience is that they typically move at a glacial pace and this is not an abnormal experience – keeping some kind of permanent reference of the historical name in the README.md would be smart for these folks, regardless of which option you go for.

I'm actually thinking this is the win-win scenario. We have to ship these weird back-versioned shims but they're stable and never need to be updated.

  • I'm confused how this is different than just using @aspnet/signalr if those are the versions someone is already depending on – changing to @microsoft/signalr for the versions that are currently published actually _increases_ the amount of work they'd need to do, since they _already_ have those versions pointed to the existing working module.

All 17 comments

Another option I just thought of:

  1. Retroactively ship @microsoft/signalr shims for the existing @aspnet/signalr packages which just re-export @aspnet/signalr types, like the other proposed shim above.

    • Pros: Anyone at any version can just move to the new package name without breaking existing apps.

    • Cons: I'm actually thinking this is the win-win scenario. We have to ship these weird back-versioned shims but they're stable and never need to be updated. Existing apps/libraries do need to move to the new package, but they can do that regardless of what version they are on.

Thoughts @davidfowl @bradygaster @BrennanConroy @mikaelm12 @halter73 ?

I'm leaning hard to option 4 here. It provides a clear migration path for everyone, doesn't require us to keep maintaining shims (since we just ship one-time @microsoft/signalr shims for existing @aspnet/signalr versions) and type identity works fine because JavaScript. We can deprecate all versions of @aspnet/signalr (assuming that doesn't mess with the shimming) to tell everybody to just flip over to @microsoft/signalr

To be clear, these new packages would be one for each currently-released version of @aspnet/signalr, and would look like this:

@microsoft/signalr package.json

{
    "name": "@microsoft/signalr",
    "version": "1.0.0",
    "main": "index.js",
    "dependencies": {
        "@aspnet/signalr": "=1.0.0"
    }
}

@microsoft/signalr index.ts

export * from "@aspnet/signalr"

So far I'm with @anurse on option 4 seeming to be the path of least resistance and maintenance.

And if we patch the npm package will we only update the new @microsoft/signalr package or do both?

And if we patch the npm package will we only update the new @microsoft/signalr package or do both

Yes, this is also my concern. I feel like servicing both will become a thorn in our side.

[Edited] Having @aspnet/signalr have a direct dependency on @microsoft/signalr feels dishonest for lack of a better word. I'm not sure if allowing two ways to get the same version of client in general is the best approach. I'm personally leaning towards just not continuing to ship the @aspnet/signalr package. It will be unfortunate that we'll end up in the same situtation as we have with the java client have both the aspnet and microsoft branded packages published and potentially confusing customers.

To clarify, as you mentioned @signalr/signalr so I just want to make sure I got your point, reworded below:

Having @microsoft/signalr have a direct dependency on @aspnet/signalr feels dishonest for lack of a better word. I'm not sure if allowing two ways to get the same version of client in general is the best approach. I'm personally leaning towards just not continuing to ship the @aspnet/signalr package. It will be unfortunate that we'll end up in the same situtation as we have with the java client have both the aspnet and microsoft branded packages published and potentially confusing customers.

I'm inclined to agree with @mikaelm12 philosophically on this, as it does seem weird (like it did years ago when Jeff described a similar pattern in NuGet) to actually be pulling the @aspnet/signalr package when you asked for the @microsoft/aspnet package. However, I think it is the path of least resistance for folks who have taken a dependency.

I'm also thinking of the community that requested we do this; the implication is folks didn't want to use it because of the implication that "ASP.NET is a requirement for SignalR." If we do it this way, the
@microsoft one will actually be the @aspnet one. Curious what those who proposed we do this would say about it - if it doesn't feel dishonest or spicy with them, I'm okay with it provided we agree it's the path of least resistance for customers and ourselves.

Didn't mean @signlar/signalr! My b. Editing it now to read @aspnet/signalr

Hey hi hello πŸ‘‹

I have some thoughts/opinions that I wanted to share:

I would not recommend doing any kind of mirroring of APIs. It creates a lot more work for a negligible amount of end-user benefit. The amount of work needed to set this up, maintain it, and – most importantly – fix it if anything goes wrong is a much higher cost than any possible end-user benefit that I am aware of. If you want to get into technical reasons, this is not a good path because it effectively gets around npm, yarn, pnpm and any other package manager's deduping of redundant modules which will end up increasing deployment/bundle sizes for end-users.

Having @microsoft/signalr have a direct dependency on @aspnet/signalr feels dishonest for lack of a better word.

I 100% agree. Being totally honest: if I'd seen this before I worked at Microsoft, my assumption would be that whoever did this was not a part of the JavaScript ecosystem and was instead just someone from .NET trying to make the JS folks happy while not really caring about them.

Regardless of the positive intent, it strays from how the JavaScript ecosystem works and comes off as uninformed/unaware – which is definitely not my experience with the SignalR team(s) so far now that I'm at Microsoft.

I'm also thinking of the community that requested we do this; the implication is folks didn't want to use it because of the implication that "ASP.NET is a requirement for SignalR."

Being totally unaware of SignalR before working at Microsoft, this wasn't quite my perception. The perception I had when learning about @aspnet/signalr was that SignalR was _only_ for ASP.NET – meaning I couldn't use it as a Node.js developer. But, I suppose that could technically also be perceived as ASP.NET being a req for SignalR usage.

As a Node.js ecosystem member, I'd like to make a suggestion – definitely weigh the pros/cons but IMO it makes the most sense _to me_:

Do option 2. Walking through _why_:

  1. No new version needs to be shipped – deprecation can be done directly without shipping a new version. See the docs on npm deprecate. This also doesn't need to be done _immediately_ if we feel it's too disruptive – we could deprecate in 365 days, or any other predefined time that we'd like to settle on.
  2. Deprecation is soft, not hard. If there are troubles for specific minor/patch versions we can un-deprecate those if absolutely needed. This should be a last resort that is taken very seriously.
  3. Since this is a semver major (two, actually?), most people should theoretically need to update anyway since npm's default is to gracefully allow for semver minor upgrades (see the caret version npm semver documentation, which is default npm behavior). As such, regardless of the scope people would have to be very intentional about upgrading their versions as a semver major version. The only downside here is that automated update tooling like Greenkeeper won't be able to auto-PR the semver major change.

Additionally, I'd like to share some reasons why I'd be _extremely hesitant_ to suggest Option 4:

  • You would need to recreate every published version of @aspnet/signalr. If there is _one single_ inconsistency in any/all of the re-publishes, you have 72 hours from publish to either delete it or re-publish because of npm's unpublish policy. This is something I would strongly recommend against.
  • The @aspnet/signalr modules will continue to work perpetually – they're not going anywhere, since they're immutable. As such, there's no real need for something like a migration path _for those older versions with the new namesapce_. If you're using those versions, you point to @aspnet/signalr. If you're using newer versions, you point to @microsoft/signalr. The only kind of migration that _should_ be happening is whatever semver major changes are introduced in the newer version – adding an additional change to a LOC that would have _already_ changed should be theoretically uncontroversial.

    • The caveat here is megacorps that have to approve every dependency manually. That said, my experience is that they typically move at a glacial pace and this is not an abnormal experience – keeping some kind of permanent reference of the historical name in the README.md would be smart for these folks, regardless of which option you go for.

I'm actually thinking this is the win-win scenario. We have to ship these weird back-versioned shims but they're stable and never need to be updated.

  • I'm confused how this is different than just using @aspnet/signalr if those are the versions someone is already depending on – changing to @microsoft/signalr for the versions that are currently published actually _increases_ the amount of work they'd need to do, since they _already_ have those versions pointed to the existing working module.

Since I have a lot of friends who maintain a non-trivial amount of packages on npm, I decided to ask about this in a general sense on Twitter. The responses so far have been super interesting:

  • general feedback so far is to do many of the steps y'all have already done + use the deprecation message once the new version is published

Thanks for your insight on the issue @bnb! Your perspective is really appreciated!

Happy to help, @mikaelm12! ❀️

Hey,

I'd like to chime in and provide some feedback–as a member of the JavaScript community and a maintainer/author of some Node packages.

@bnb had already provided a much more detailed response.

So, definitely go for option 1: publish the new package and deprecate the old one, making sure to provide the right communication about this change and how they can get the new package. This, of course, would be considered as a breaking change, so developers would anyway have to check the new API deprecations and update their existing code. In the process, you would have an extra step which updating their package.json with the new scope.

Cheers.

This is great perspective! I'm mostly happy to defer to the JS community experts here, but I do want to make sure I have clarity on things here.

  1. No new version needs to be shipped – deprecation can be done directly without shipping a new version.

Unfortunately this isn't true since _none of our existing published versions are considered deprecated_. They remain fully supported and will be patched in accordance with the .NET Core Support Policy. I think that marking them deprecated would be misleading. Of the current versions, only the 3.0 preview releases would be actually deprecated. The 1.0.x releases (aligned with .NET Core 2.1) are supported until at least August 2021 and the 1.1.x releases (aligned with .NET Core 2.2) are supported until December of this year/

This is why option 2 requires the publishing of a new version to indicate to those interested in updating how to actually do so. We could in theory wait for the 1.1.x releases to go end-of-life and deprecate those then.

  • The @aspnet/signalr modules will continue to work perpetually – they're not going anywhere, since they're immutable. As such, there's no real need for something like a migration path _for those older versions with the new namesapce_.

It's not entirely true that there's no real need to provide this migration path. Consider a _library_ that has a dependency on @aspnet/signalr. In general, we strive to preserve compatibility across major version boundaries unless necessary, and it's my understanding that we haven't made any major breaking changes in 3.0 to the client API. A library or app written against @aspnet/signalr 1.0 should be expected to work against @microsoft/signalr 3.0. Unfortunately there's no way to express that (to my understanding at least) since this is a completely new package identity.

I'm not clear on how simply publishing a new package doesn't end up bifurcating the ecosystem, though maybe the ecosystem (libraries depending upon the SignalR client) is not large enough to be a concern here.

if I'd seen this before I worked at Microsoft, my assumption would be that whoever did this was not a part of the JavaScript ecosystem and was instead just someone from .NET trying to make the JS folks happy while not really caring about them.

Perhaps this is just naivetΓ© regarding how the JS community organizes. That's why we want to get this information, to find out what the community would find most useful.

Certainly, option 2 doesn't exclude us from taking actions listed in the other options though. I'm quite happy to move forward with that plan and if we hear that this is causing issues in the ecosystem, we can move on.

This is why option 2 requires the publishing of a new version to indicate to those interested in updating how to actually do so.

If this is the path that ends up being chosen, my suggestion is to publish a patch version that includes a new message in the README.md file – effectively, for display only on npmjs.com.

My only concern with this is that you're publishing something that end-users would expect to be code/improvement, but it's actually a misdirection of how to use the real thing. Not a massive deal if it's a few lines in README.md and a patch version, though! πŸ‘

Unfortunately this isn't true since none of our existing published versions are considered deprecated. They remain fully supported and will be patched in accordance with the .NET Core Support Policy. I think that marking them deprecated would be misleading. Of the current versions, only the 3.0 preview releases would be actually deprecated. The 1.0.x releases (aligned with .NET Core 2.1) are supported until at least August 2021 and the 1.1.x releases (aligned with .NET Core 2.2) are supported until December of this year/

This is totally fine, then - no deprecation needs to occur. Current consumers that are unwilling/unlikely to upgrade (there are always some!) can continue on their happy path. I would highly recommend planning on shipping deprecation at the time where the relevant versions go EOL ❀️

I'm not clear on how simply publishing a new package doesn't end up bifurcating the ecosystem, though maybe the ecosystem (libraries depending upon the SignalR client) is not large enough to be a concern here.

It does to some extent, but now is a good time to do it for a few reasons:

  1. SignalR downloads are still relatively low (I've seen 70k installs be generated by ~10 medium sized deployments)
  2. The decision is messaging that SignalR is for more than "just" asp.net to the broader JavaScript ecosystem.
  3. Now is a good time to do this since you have a base of users but not an oversized one where you have little to no flexibility (as is the case with some bigger projects like Node.js, request, Webpack, or Babel)

Additionally, one of the core tenets of the JavaScript ecosystem is iterating fast. There will definitely be some bifurcation for a short period, but the newer always outgrows the older rather rapidly. 6to5 => Babel is an example of this that had relatively similar reasoning behind it, and was at a much larger scale. I've not heard about 6to5 or seen real-world usage in years. Ironically, they did this again with babel => babel-cli several versions later.

Perhaps this is just naivetΓ© regarding how the JS community organizes. That's why we want to get this information, to find out what the community would find most useful.

❀️

Triage decision: We will do option 1 for now. It doesn't prevent us from doing other things to adjust. @bradygaster will look at the Dependents for @aspnet/signalr and consider reaching out directly to the frequently-downloaded ones.

Closing since there's no engineering action!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

FourLeafClover picture FourLeafClover  Β·  3Comments

rbanks54 picture rbanks54  Β·  3Comments

groogiam picture groogiam  Β·  3Comments

snebjorn picture snebjorn  Β·  3Comments

guardrex picture guardrex  Β·  3Comments