This morning I stumbled on a blog post from Krzysztof Kozmic .NET OSS Dependency Hell. The title caught me straight: dependency + .NET is pretty dear to me. Krzysztof explains a problem users of Castle Project have (hence a problem developers of Castle Project have to fix).
The Castle Project is the union of several OSS projects (ActiveRecord – MonoRail – MicroKernel/Windsor – Common Components – DynamicProxy …). On the Castle Mission page I can read:
Castle should not be all-or-nothing. The developer can use the tools he wants to use, and at the same time, use different approaches in different areas of his application in a peaceful manner.
And here is the complain formulated by a Castle user:
I just cannot upgrade. I want to use ASP.NET MVC version 2.0 but my upgrade path is just too complicated. I have used too much OSS.
My understanding of the problem is that integrating with Castle generates maintenance friction. I have to say that I never used Castle project directly. I am going to add my 2 cents to the debate just by extracting facts from the Castle Project code base itself.
So I downloaded Castle assemblies and analyzed it with NDepend. There are 23 Castle assemblies made of 212 853 IL instructions (meaning around logical 33K Lines of Code). My first impression is that while 33K LoC reveals a huge OSS development and maintenance effort, 33K LoC can be easily compiled in a single assembly. My team has a 45K LoC assembly that compiles in 5 seconds and weights 1.5MB (it would be much lighter without all embedded resources btw). Here is the graph of dependencies between Castle assemblies:
While the Graph is pretty informative, I have a tendency to prefer browsing the Dependency Matrix to understand the structure of a code base. In the Graph I removed dependencies to Tier assemblies used by Castle, because it became unreadable. But the Dependency Matrix scales easily and in this particular case, I estimate that seeing how Castle assemblies used tier assemblies is essential.
Tier assemblies usage in the Castle Project is essential because it shows that under the 33K LoC of the Castle Projects, lives a lot more OSS code (NHibernate, Boo infrastructure, log4net, NVelocity… and also Lucene and Antlr runtime not visible here because not directly used by Castle). I did an experiment: aggregating all the underlying OSS assemblies (meaning having only .Net Fx assemblies in the tier assemblies blue column) and I obtained a much larger code base made of 1 141 754 IL inst (about 175K LoC).
My 2 cents proposition is that the Castle project code base should be re-partitioned with 2 goals in mind.
- First, having a minimal number of assemblies. Here I mean that each assemblies in the new partition should have a relevant physical reason to exist. I wrote about Advices on Partitioning code through .NET Assemblies. (separating 2 tools is a logical reason, avoiding loading accidentally too much unnecessary code is a physical reason).
- Second, due to the dependencies on large tier OSS project outside of Castle (like NHibernate or Boo) the partition should be done in a way, that a large tier OSS project cannot get loaded accidentally at runtime, if the code of the Castle feature requested by the user doesn’t need it. For example Boo is used exclusively by Castle.MonoRail. Thus, here we have a need to separate Castle.Monorail because we don’t want to load the 1.5MB Boo assemblies accidentally, if not needed.
With this approach I don’t know how many assemblies would be needed, maybe 4 or 5. Certainly, such optimal partition would be incoherent with the Castle project initial approach of proposing relatively independent tools (Castle should not be all-or-nothing). Maybe some tools would spawn several assemblies, and a single assembly might contain several tools. But from the Castle user point of view, with a set of Castle assemblies reduced to something like 4 or 5 assemblies, the friction of having numerous assemblies references to maintain is gone.
As a side note, a public or internal Castle.Core.dll assembly referenced by all Castle assemblies, might or might be not needed. A deeper analysis would be required for that. Also a single CastleProject.dll assembly would be great, but chances of loading accidentally a lot of unnecessary code at runtime (because of JIT compilation) would then increase a lot.
From the Castle developers point of view, the impact of such change would be huge.
- First, they would need to synchronize all tools for each new release. I don’t know how each tools upgrade is released, but synchronizing all tools upgrade would simplify the update for users.
- Second, they would need to care for dependencies internal in the Castle assemblies themselves. But even more essential, they will need to avoid development friction generated by living in the same compilation unit. An idea would be to have more smaller compilation units used for development and unit testing, where all the source code gets integrated in some few larger compilation units for integration. And I don’t mean using ilmerge here. Things must be aggregated properly.
This was my 2 cents with my external view from the Castle Project. It is not a –all or nothing- proposition, and what is essential is to propose the Castle API through less and larger assemblies. Certainly an insider might yield at this proposition arguing –some internal Castle Project constraints here– but the point is to make the life of Castle Project users easier by reducing the maintenance friction of referencing Castle.