We’ve seen the fights on twitter about strong naming of assemblies. There are arguments from both parties. Strong named assemblies can be GAC-ed; but hey, the trend is pretty much anti-GAC – dnx lets you ship the runtime with your app. Right? Right? I’ve not bothered much with strong naming – a) I can’t see what it gives you, b) it’s a hassle that’ll take time (however little) to set up. Most places “requiring” strong naming do it to satisfy some checklist. Anyhow, enough of that.
Proponents of strong naming mention that you have nothing to lose. Strong name something, and Nuget updates, etc. work with binding redirects, etc. if you mention the correct public key. Entity Framework’s package is an example of this. I hear it’s strong named (Haven’t checked in a while…barge pole distance from that sort of stuff and all…), yet things seem to work. So, might as well make things SNed, right?
Oh God, no. Please no. At least for existing public packages. And here’s why:
Taking a Package from Non SN to SN will Break End App Nuget Updates
Let me explain.
I have a nice little Sql Server based event store supporting subscriptions, competing consumers, etc. called Res. Res runs as a Windows service providing TCP access to publish, store, and subscribe to events (shameless plug): https://github.com/heartysoft/res
To not have to deal with utter networking krap, I used NetMQ (which is the .NET native port of ZeroMQ). And as with most things TCP, for easy consumption, I also provide a NuGet package – Res.Client. The server runs as its own process (though Res.Core allows for a self hosted version useful for testing). However, the client is a library. And here lies the issue – it uses NetMQ too.
The current (0.0.15) version of Res.Client uses NetMQ 18.104.22.168. If an app adds the Res.Client nuget, that’s the version of NetMQ they get. NetMQ 22.214.171.124 is NOT strongly named. Now if the app developer goes to “update” the NuGet package (or say, does an Update All), NetMQ will update itself to 126.96.36.199. Being a patch release, that should be fine. No breaking changes. All should be peachy. And the NuGet update goes smoothly. Even running the app goes smoothly, until anything in Res.Client is used. Why?
The reason is that 188.8.131.52 (actually 184.108.40.206 onwards), NetMQ started strong naming the NuGet dll. What does that bring about? Well, it means ANY app using ANY library built targeting 220.127.116.11 can never update the NetMQ package unless that library updates itself to use a SNed version of NetMQ as well. Even binding redirects won’t help here – since 18.104.22.168 has no public key token, and 22.214.171.124+ has, as far as .NET is concerned, they’re completely different. And making the same NuGet package provide completely different (in .NET’s eyes) libraries should be a no no.
Please Don’t Strong Name a Nuget Package That Isn’t
This causes transitive dependency update nightmares. Users, and other library builders building on your package won’t be able to trust your SemVer. End application users won’t be able to update your package unless all libraries using it that are in the project have also updated themselves. Forget the if – just don’t do it. Different public keys (including null) mean a completely different “thing” to .NET. Why publish that as the same NuGet package when your taking so much care to SemVer your package?
What Can I Do?
I reported this here https://github.com/zeromq/netmq/issues/109 to which Som outlined two options (and I’ve added one):
1. Compile NetMQ from source without SN: This means libraries should NOT use the NetMQ NuGet, rather include a compiled binary. That will add complications if the end app is also using NetMQ through NuGet. The workaround for this would be to ILMerge the compiled version to make that one protected. Not only is this a tax on the library using NetMQ, it also means the end app can’t benefit from non-breaking bug fixes of NetMQ distributed through NuGet. If NetMQ is being used directly, it’s a (poor) option, but for libraries to be built on top of it, it’s a very bad one.
2. Publish two NuGet packages – one SN and one non-SN: This seems more cumbersome, but in my opinion, it’s the right thing to do. Adding strong naming is creating a “new” thing to .NET. As such, the NuGet package should be new too. This keeps the NuGet update compatibility for existing things, while giving those that want SN to have it.
3. Declare non-SN NetMQ dead, have only SN: since the update path is broken, I see it as a breaking change. Bump the major version, and communicate the change well in advance. Yes, the API hasn’t changed, but the binary has a new public key – it’s got a new identity. A new identity means binding redirects, etc. won’t work. Keeping something that can easily be accidentally updated to and cause runtime issues (it won’t get caught at compile time, and you’ll need Fusion logs to figure out what the heck’s going on) as a SemVer “compatible” thing is just plain wrong.
This is NOT about NETMQ
There’s nothing unique to NETMQ here. [NetMQ is awesome, as is ZeroMQ and the entire Zero eco-system.] If you have a public package that’s not strong named, and you want to strong name it, please keep these things in mind. I remember log4net having similar issues in the past. These aren’t issues if your library is only going to be used by end applications. However if you expect other libraries to make use of yours to build a good eco-system, making these sorts of silent breaking changes will not help your cause. Whichever side of the strong naming debate you’re on – if you change the public key of your package, ensure that the new one is not SemVer compatible with the old one. Or better yet, create a new package. (Or even better, throw strong naming in the bin).