TL;DR: .Net isn’t ready for the brave new world of componentization and smaller, more rapid updates quite yet, but I have some suggestions based on the development of FubuMVC.
To recap, I’m writing a series of posts about the FubuMVC community’s technical experiences over the past five or so years after deciding to give up on new development after the shortly forthcoming 2.0 version. So far, I’ve discussed…
- Our usage of the Russian Doll model and where we fell in the balance between repetitive ceremonial code and using conventions for cleaner code
- Command line bootstrapping, polyglot programming, and the value in standardizing codebase layout for the sake of tooling (I say yes, .Net community says no)
- Lots of DevOps woes trying to develop across multiple repositories using Nuget, TeamCity, and our own Ripple tool.
…and a lot of commenters have repeatedly and quite accurately slammed the documentation (that’s shooting fish in a barrel) and got strangely upset over the fact that we used Rake for our own build automation even though FubuMVC itself had no direct coupling to Ruby.
Strong Naming is Hamstringing Modularity in .Net
If you follow me on Twitter, you probably know that I hate strong naming in .Net with a passion. Some of you might be reading this and saying “it only takes a minute to set up strong naming on projects, I don’t see the big deal” and others are saying to yourselves that “gosh, I’ve never had any problem with strong naming, what’s all the teeth gnashing about?”
Consider this all too common occurrence:
- Your application uses an OSS component called FancyLogging and you’re depending on the latest version 2.1.5.
- You also use FancyServiceBus that depends on FancyLogging version 2.0.7.
- You might also have a dependency on FancyIoC with in turn depends on a much older version of FancyLogging 2.0.0.
Assuming that the authors of FancyLogging are following Semantic Versioning (more on this in a later post), you should be able to happily use the latest version of FancyLogging because there are no semantically breaking changes between it and the versions that FancyServiceBus and FancyIoC were compiled against. If these assemblies are strong named however, you just set yourself up for a whole lot of assembly version conflicts because .Net matches on the entire version number. At this point, you’re going to be spending some quality time with the Fusion Log Viewer (you want this in your toolbox anyway).
Strong naming conflicts are a common issue when your dependencies improve or change rapidly, and you upgrade somewhat often, and especially when upstream dependencies like log4net, IoC containers, and Newtonsoft.Json are also used by your upstream dependencies. Right now I think this problem is felt much more by shops that depend more heavily on OSS tools that don’t originate in Redmond, but Microsoft itself is very clearly aiming for a world where .Net itself is much more modular and the new, smaller libraries will release more often. Unless the .Net community addresses the flaws in strong naming and adopts more effective idioms for Nuget packaging, my strong naming conflict versions are about to come to a mainstream .Net shop near you.
Strong Naming Woes and Workarounds
While I’ve never had any trouble whatsoever with using it, at one point a couple years ago assembly conflicts with Newtonsoft.Json was my single biggest problem in daily development. Fortunately, I encounter very little trouble today due to Newtonsoft.Json’s strong naming. Why was this one library such a huge headache and what can we learn from how myself and the .Net community as a whole alleviated most of the pain?
First off, Newtonsoft.Json was and is strong named. It’s very commonly used in many of the other libraries that the projects I work with depend on for daily development, chief among them WebDriver and RavenDb. I also use Newtonsoft.Json in a couple different fubumvc related projects (Bottles and Storyteller2). Newtonsoft.Json has historically been a very active project and releases often. Really due to its own success, Newtonsoft.Json became the poster child for strong naming hell.
Consider this situation as it was in the summer of 2012:
- We depended upon WebDriver and RavenDb, both of which at that time had an external dependency upon Newtonsoft.Json.
- WebDriver and RavenDb were both strong named themselves
- Both WebDriver and RavenDb were releasing quite often and we frequently needed to pull in new versions of these tools to address issues and subsequent versions of these tools often changed their own dependency versions of Newtonsoft.Json
- Our own Storyteller2 tool we used for end to end testing depended upon Newtonsoft.Json
- Our Serenity library we used for web testing FubuMVC applications depends upon WebDriver and Storyteller2 and you guessed it, we were frequently improving Serenity itself as we went along
- We would frequently get strong naming conflicts in our own code by installing the Newtonsoft.Json nuget to an additional project within the same solution and getting a more recent version than the other projects
I spent a lot of time that summer cursing how much time I was wasting just chasing down assembly version conflicts from Newtonsoft.Json and WebDriver and more recently from ManagedEsent. Things got much better by the end of that year though because:
- WebDriver ilmerge’d Newtonsoft.Json so that it wasn’t exposed externally
- WebDriver, partially at my urging, ditched strong naming — making it much easier for us
- RavenDb ilmerge’d Newtonsoft.Json as well
- We ilmerge’d Newtonsoft.Json into Storyteller2 and everywhere else we took that as a dependency after that
- Newtonsoft.Json changed their versioning strategy so that they locked the assembly version but let the real Nuget version float within semantically versioned releases. Even though that does a lot to eliminate binding conflicts, I still dislike that strategy because it’s a potentially confusing lie to consumers. The very fact that this is the recommended approach by the Nuget team themselves as the least bad approach is a pretty good indication to me that strong naming needs to be permanently changed inside the CLR itself.
- ManagedEsent was killing us because certain RavenDb nugets smuggle it in as an assembly reference, conflicting with our own declared dependency on ManagedEsent from within the LightningQueues library. Again, we beat this with ilmerge’ing our dependency on ManageEsent into LightningQueues and problem solved.
- With Ripple, we were able to enforce solution wide dependency versioning, meaning that when we installed a Nuget to a project in our solution with Ripple it would always try to first use the same Nuget version as the other projects in the solution. That made a lot of headaches go away fast. The same solution wide versioning applied to Nuget updates with Ripple.
My advice for library publishers and consumers
I definitely feel that much of the following list is a series of compromises and workarounds, but such is life:
- Be cautious consuming any strong named library that revisions often
- Don’t apply strong naming at all to your published assemblies unless you have to
- Don’t bundle in secondary assemblies into your Nuget packages that you don’t control — i.e., the loose ManagedEsent assembly in RavenDb packages problem or this issue in GitHub for Ripple
- Prefer libraries that aren’t strong named if possible (e.g., why we choose NLog over log4net now)
- Privately ilmerge your dependencies into your libraries if the consumers when possible, which I’ll freely admit is a compromise that can easily cause you other problems later and some clumsiness in your build scripts. Do make sure that your unit and integration tests run against the ilmerge’d copy of your assembly in continuous integration builds for best results
- Do the Newtonsoft.Json versioning trick where the assembly version doesn’t change across releases — even though I hate this idea on principle
Rip out the Strong Naming?
We never actually did this (yet), but it’s apparently very possible to rip strong naming out of .Net assemblies using a tool like Mono.Cecil. We wanted to steal an idea from Sebastien Lambla to build a feature into Ripple where it would remove the signing out of assemblies as part of the Ripple package restore feature. If I do stay involved in .Net development and the fine folks in Redmond don’t fix strong naming in the next release of .Net, I’ll go back and finally build that feature into Ripple.
My Approach to Strong Naming
We never signed any of the FubuMVC related assemblies. FubuMVC itself was never a problem because it’s a high level dependency that was only used by the kind of OSS friendly shops that generally don’t care about strong naming. StructureMap on the other hand is a foundational type of library that’s much more frequently used in a larger variety of shops and it had been signed in the releases from (I think) 2.0 in 2007 until the previous 2.6.4 release in 2012. I still decided to tear out the strong naming as part of the big StructureMap 3.0 release with the thinking that I’d support a parallel signed release at some point if there was any demand for strong naming — preferably if the people making those demands for strong naming would be kind enough to submit the pull request for the fancier build automation to support that. I can’t tell you yet if this will work out, and judging from other projects, it won’t.
What about Security! Surely you need Strong Naming!
For all of you saying “I need strong naming for security!”, just remember that many OSS projects commit their keys into source control. I think that if you really want to certify that a signed assembly represents exactly the code you think it is, you probably need to compile it yourself from a forked and tagged repository that you control. I think that the signed assemblies as security feature of .Net is very analogous to the checked exceptions feature in the Java language. I.e., something that its proponents think is very important, a source of extra work on the part of users, and a feature that isn’t felt to be important by any other development community.
To balance out my comments here, last week there was a thread on a GitHub issue for OctoKit about whether or not they should sign their released assemblies that generated a lot more pro-assembly signing sentiment than you’ll find here. Most of the pro-strong naming comments seem to be more about giving users what they want rather than a discussion of whether or not strong naming adds any real value but hey, you’re supposed to make your users happy.
What about automatic redirects?
But Jeremy, doesn’t Nuget write the assembly redirects for you? Our experience was that Nuget didn’t really get the assembly redirects right as often as not and it still required manual intervention– especially in cases where we were using config files that varied from the App.config/Web.config norm. There is some new automatic redirect functionality in .Net 4.5.1 that should help, but I still think that plenty of issues will leak through and this is just a temporary bandaid until the CLR team makes a more permanent fix. I think what I’m saying about the automatic redirects is that I grew up in Missouri and you’ll just have to show me that it’s going to work.
My wish for .Net vNext
I would like to see the CLR team build Semantic Versioning directly into the CLR assembly binding so that the strong named binding isn’t quite so finicky such that the CLR can happily load version 3.0.3 whenever that’s the present version even though the declared version in other assemblies is 3.0.1 rather than matching on exactly version 3.0.1 only. I’d like to see assembly redirect declarations in config files go away entirely. I think the attempts to build automatic redirects into VS2013 are a decent temporary patch, but the real answer is going to have to come at the CLR level.
So the DevOps topics of strong naming, versioning, and continuous integration across multiple repositories is taking a lot more verbiage to cover than I anticipated and long time readers of mine know that I don’t really do “short” very well. In following posts I’ll talk about why I think Semantic Versioning is so important, more about how Ripple solved some of our Nuget problems, recommendations for how to improve Nuget, and a specific post on doing branch per feature across multiple repositories.