.NET OSS dependency hell

Paul, whom some of you may know as the maintainer of Horn project, left a comment on my blog, that was (or to be more precise – I think it was) a continuation of series of his tweets about his dissatisfaction with the state of affairs when it comes to dependencies between various OSS projects in .NET space, and within Castle Project in particular.

paul_twitter

I must say I understand Paul, and he’s got some valid points there, so let’s see what can be done about it.

Problems

One of the goals of Castle Project from the very beginning has been modularity of its elements. As castle main page says:

Offering a set of tools (working together or independently) and integration with others open source projects, Castle helps you get more done with less code and in less time.

How do you achieve modularity. Say you have two projects, Foo and Bar that you want to integrate. You could just reference one from the other.

eb1c2cc[1]

This however means that whenever you use Foo, you have to drag Bar with you. For example, whenever you want to use MonoRail, you’d need to drag ActiveRecord with it, along with entire set of its dependencies, and their dependencies, etc.

Instead you employ Dependency Inversion (do not confuse with Dependency Injection). You make your components depend on abstractions, not the implementation. This however means, that in .NET assembly model, you need to introduce third assembly to keep the abstractions in.

51067fb6[1]

Now we have 3 assemblies instead of 2 to integrate two projects. Within Castle itself common abstractions are being kept in Castle.Core.dll. But what if we want to take more direct advantage of one project in another project still maintaining the decoupled structure? We need to extract the functionality bridging the two projects to yet another assembly. Tick – now we have 4 of them.

607f68d4[1]

In this case the FooBar project would be something like ActiveRecord integration facility, which integrates ActiveRecord with Windsor.

When you mix multiple projects together you enter another problem – versioning.

Say you want to integrate few projects together, some of which are interdependent (via bridging assemblies, not shown here for brevity)

69f20a13[1]

Now, once a new version of one of the projects is released, you either have to wait for all the other projects to update their dependency to the latest version, do it yourself (possibly with some help from Horn), or stick to the old version. The situation gets even more complicated when there were some breaking changes introduced, in which case plain recompilation will not do – some actual code would need to be written to compensate for that.

These are the main issues with this model, let’s now look at possible solutions.

Solutions

First thing that comes to mind – if having some assemblies means you’ll need even more assemblies, perhaps you should try to minimize that number? This has already come to our minds. With last wave of releases we performed some integration of projects. EmailSender got integrated into Core, one less assembly. Logging adapters for log4net and nlog were merged into core project, which means they still are separate assemblies (as they bridge Castle with 3rd party projects) but they’re now synced with Core and are released with it, which means this is one less element in your versioning matrix for you to worry about. Similar thing happened with Logging Facility, which now is versioned and released with Windsor itself.

For the next major version, there are suggestions to take this one step further. Merge DynamicProxy with DictionaryAdapter and (parts of) Core into a single assembly; Merge Windsor and MicroKernel (and other parts of Core) into an other assembly. With that you get from 5 assemblies to 2.

That reduces Castle’s internal dependencies, but what about other projects that depend on it? After the recent release, we started a log of breaking changes, along with brief explanation and suggested upgrade paths, to make it easier for applications and frameworks to upgrade. We have yet to see how this plays out.

What else can be done?

This is the actual question to you? What do you think can be done, for Castle specifically, but more broadly – for entire .NET OSS ecosystems to make problems Paul mentioned go away, or at least make them easier to sort out?

Comments

Henry says:

Well,

This is a side effect of releasing the project (the dependency issues).

In the past, or people didn’t update the libs so often or they we’re rolling with the trunk (which was very easy to deal with you were somewhat initiated).

Probably planning the releases more carefully will easy some of the stress.

Paul Cowan says:

This is a really nicely articulated presentation of the problem.

Hornget is our attempt at solving the problem.

I just cannot see anyway apart from building from the source of leveling the field.

I think OSS needs to work together.

One idea that I have is to use a central repo where everybody gets their latest .dlls from.

You only have to experience the number of different boo .dlls doing the rounds to realise how widespread the problem is.

John Simons says:

I’m wondering if using ilmerge /internalize is the solution for this problem.
Rhino.Mocks seems to use it quite well, and it works.

Just a thought 🙂

I’ve just read a similar post from Jeremy Miller (in context of FubuMVC). I strongly believe that sooner or later we, as .NET community, will manage to work out a solution for managing OSS dependencies. Maybe via this ‘resurected’ Nu project?

I think that ilmerge is not a good idea. It tends to hide problem rather than solving it. When encountered a problem with too many assemblies, I decided to rethink my internal dependencies structure. In the effect I managed to reduce assembly count by a factor of 3 without really harming the abstraction I cared about. I only removed the ones which were misplaced on never used.

I also think that OSS project tend to produce much more abstractions than it is actually needed and this is a side effect of the uncertainty we are dealing with in everyday coding. Look at NHibernate vs Linq to Entities. NHib is all about extension points while Linq to Entities have almost none.

Another example of approach to abstractions is NServiceBus vs MassTransit. Both frameworks solve similar problems and have similar capabilities. In NSB everything that could possibly change is abstracted. In MassTransit, everything that actually _has changed_, is abstracted. NSB has in the effect about 10 times more assemblies/projects than MassTransit. Downside is that it is nearly impossible to manage without ilmerge. But there is also an advantage: when you want to change something and you know how, you can do it. In MassTransit it is nearly impossible without changing the source.

Fred Legrain says:

Good post Krzysztof, thanks.

lpodolak says:

What else can be done? I have one wish for every oss project leaders (to whom I give respect anyway) so that they provided a signed versions of their assemblies by default. The problem with dependency hell for me started when I wanted to strongly sign web app that depended on mvccontrib. And I gave up – even tricks with decompilation to il and signing by myslelf didn’t come to the rescue.
Thankfully, castle is signed by default.