Advices on partitioning code through .NET assemblies

The tenet is: reduce
the number of your .NET assemblies to the strict minimum
. Having a single assembly is the ideal number. This is for example
the case for Reflector 
or NHibernate that both come as a
single assembly.

 

A lot have been said on
this topic. I dig in this article
why using more namespaces and less
assemblies for componentization is a good thing. Jeremy Miller does it so in
this blog post
. The point is that assemblies are physical while namespaces are logical. As a consequence, having N
assemblies multiplies by N the burden of dealing with physical things. This
burden consists of referencing N assemblies from a Visual Studio project, slowing
down significantly compilation of N Visual Studio projects, managing the
deployment for N files, need for the CLR to do N CAS security checks at startup
time, etc… Personally I have seen applications with up to 750 assemblies! Quote from Jeremy Miller:
I took 56 projects one time and consolidated them down to 10-12 and cut
the compile time from 4 minutes to 20 seconds with the same LOC

 

I would like to discuss
here the motivations behind creating an assembly. I will then illustrate these
reasons through the choices we did for assemblies of the NDepend
code base. In an effort to reduce
the number of assemblies in your shop, it is a good thing to find a valid reason for the existence of each assembly of your code base. If no solid reasons can be
found you’ll find room for merging assemblies.

 

Valid and Invalid reasons to create an assembly

 

Valid reasons to create
an assembly:

  • Tier separation: Need to
    run some different pieces of code in different AppDomain or process. The idea is
    to avoid overwhelming the precious Window
    process memory with large pieces of code not needed.
  • Potential for loading large
    pieces of code on-demand. This is an optimization made by the CLR: assemblies are loaded on-demand. In other
    words, the CLR loads an assembly only when a type or a resource contained in it
    is needed for the first time. Here also you don’t want to overwhelm your Window process memory with large pieces
    of code not needed most of the time.
  • Framework features
    separation. In case of very large framework, users shouldn’t be forced to embed
    every features into their deployment package. For example, most of the time an
    ASP.NET process doesn’t do some Window Forms and vice-versa, hence the need for
    2 assemblies System.Web.dll and System.Window.Forms.dll. This is valid only
    for large framework with assemblies sized in MB. For example the NUnit http://www.NUnit.com framework certainly does
    not need 25 assemblies for 15.000 Lines of Code! Quote from Jeremy Miller:
    Nothing is more irritating to me than using 3rd party toolkits that
    force you to reference a dozen different assemblies just to do one
    simple thing.
  • AddIn/PlugIn model, need
    for interface/factory/implementation physical separation.
  • Test/application code separation. If you are not releasing source code but only assemblies, you likely don’t want to release tests. Not releasing test assemblies make this easy.
  • When several assemblies have
    been created for the reasons above, they likely need to share some common code.
    Such shared code must be placed in a dedicated shared assembly.

We could add assembly as a
unit of versioning but IMHO, the need for versioning is a subset of all these
reasons enumerated above.

 

Invalid reasons to create
an assembly:

  • Assembly as unit of
    development, or as a unit of test. Modern Source Control Systems make it easy
    for several developers to work simultaneously on the same assembly (i.e the
    same Visual Studio project). The unit should be here the source file.
  • Automatic detection of
    dependency cycles between assemblies by MSBuild and Visual Studio. There are tools
    such as
    NDepend that
    can detect dependency cycles between namespaces or types of an assembly.
  • Usage of internal
    visibility to hide implementations details. This public/internal visibility
    level is useful when developing a framework where you want to hide
    implementation details to the rest of the world. Your team is not the rest of
    the world, so you don’t need to create some assemblies especially to hide some implementations
    details.
  • Usage of internal
    visibility to prevent usage from the rest of the application. If you want to
    prevent usage and thus control the structure/dependencies of your code base, you should
    better use some dedicated tools such as
    NDepend .

If you are interested in
reducing the number of your Visual Studio projects/assemblies, I would suggest
reading this blog post Hints on how to componentized existing code.
It shows how to use some NDepend dependencies features to get some hints about
which sets of assemblies should be merged.

 

 

A case study

 

NDepend code base is
split across 11 assemblies and here is the dependency diagram of NDepend assemblies
(made by NDepend itself):

 

 

 

Something you might
notice is the XDepend term. We are currently building the XDepend product (aka NDepend for Java), that will be released in 2009. More information are
available on the official website
http://www.XDepend.com and I will talk more about this in
the future. This XDepend/NDepend duality leads to 2 different deployment
packages for the 2 products and this is why there are NDepend.Console.exe, VisualNDepend.exe,
NDepend.Platform.DotNet.dll on one
hand and XDepend.Console.exe, VisualXDepend.exe, XDepend.Platform.Java.dll on the other hand.

 

The need for 4 exe
instead of 2 was needed mostly for terminology. XDepend.Console.exe certainly makes more sense for an XDepend user than NDepend.Console.exe. All
these 4 executables are almost empty in terms of code. The console ones
initialize the platform (Java or .NET) and start the analysis implemented in NDepend.Analysis.dll while the Visual
ones initialize the platform and start the UI, implemented in NDepend.UI.dll.

 

The need to initialize
the platform (.NET or Java) is a motivation for isolating the code specific to
each platform. Hence the need for the 2 assemblies NDepend.Platform.DotNet.dll and XDepend.Platform.Java.dll.
This separation will also make easy potential future platform support (C++,
Delphi…).

 

The tool comes with 2
primary usages, analyze code and digging into analysis results through the UI.
This is the motivation for having 2 different executable assemblies: NDepend.Console.exe and VisualNDepend.exe on one hand, and 2
different libraries, NDepend.Analysis.dll
and NDepend.UI.dll on the other hand.
The idea is to avoid having both these assemblies loaded inside the same
process.

 

NDepend.Analysis.dll and NDepend.UI.dll both rely
on a lot of common code, core domain objects + many helper/util code, hence the
need for creating the NDepend.Framework.dll
assembly.

 

NDepend.CQL.dll is a lightweight assembly (less than 10KB) that contains the plumbing for
declaring CQL rules
directly inside the source code.
Not only this assembly NDepend.CQL.dll
is used by NDepend users who harness this possibility, but also we use it
extensively to declare our own constraints in our code. For users who which to
declare CQL constraints inside their source code, it is more convenient to link
with a single and lightweight assembly, hence the need for NDepend.CQL.dll.

 

Finally, the NDepend.AddIn.dll assembly contains the
plumbing needed to register the NDepend VisualStudio and Reflector addin
. This assembly references big assemblies such as the Reflector.exe assembly. In order to prevent loading by mistake Reflector.exe in one of the NDepend process, it was a good thing to create a dedicated NDepend.AddIn.dll assembly. Also, addin assemblies are registered in VisualStudio and Reflector
through their names and the name NDepend.AddIn is well-suited.

 

 

 

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.UdiDahan.com Udi Dahan: The Software Simplist

    Patrick,

    I’d suggest that we separate from the development artifact (VS project) and the deployment artifact (assembly).

    The fact that Visual Studio automatically compiles projects to assemblies is good for RAD but less so for larger-scale development.

    Personally, I use ILMerge to merge the results of multiple project comilations into a single physical assembly.

    This makes it easy to see references between different development artifacts without going into a different tool – something that makes code reviews that much quicker and easier.

    That being said, I still think NDepend is an invaluable tool.

  • Kyle Baley

    This is good advice. The VS solution should be considered a logical organization of your project to help during development.

    This can also be achieved with an automated build process. With NAnt, you can compile all relevant files into a single assembly using the task rather than compiling the solution with msbuild.

    http://www.igloocoder.com/archive/2008/02/21/how-do-you-build-your-application-details.aspx

  • http://blog.fohjin.com Mark Nijhof

    Even now that the source control systems are getting better I still have the occasional conflict in the project files when multiple developers are working on it at the same time. Because of this we decided to create some extra projects that share the base namespace (abc.xyz and abc.qwe) while in active development and then later merge the code into one project again. Which is easy with R#.

    Another thing is the separation between interfaces and implementations. When using an IoC I like to have my interfaces in a different dll as my implementations at the very least they will be in a different namespace so they can easily be moved at a later stage. I like this because it gives the ability to change the implementation and the configuration (dll) and leave the rest alone.

    What are your thoughts on this?

    -Mark

  • Patrick Smacchia

    Mark concerning the interface (plugin pattern) I indeed deem that creating assemblies is a good practice as I said in the post.

    Concerning crating temporary projects, it is a dangerouis practices such as any temporary flawed state. there is still the risk to forgot or not have the time to go bak to a clean state later.

  • Brian

    It’s funny how 4 years ago when I was screaming about how many people were adding unnecessary complexity to their systems by adding so many assemblies under the ruse of ‘loose coupling’ and ‘greater flexibility’ are now coming back to their senses and realizing that there is a bell curve to it.

    However, be careful with this one:
    Framework features separation. In case of very large framework

    Take a look at Microsoft Patterns and Practices 4.1 – it has reached the point that if you aren’t already indoctrinated with EL, that the ramp up time and understanding of it is HUGE because there are so many 10′s of assemblies that come with this thing.

    The premise behind it being – you should be able to just add the assemblies you need to your deployment, and that you won’t have to have extra features you don’t need – but it’s so darn fine grained you have a monstrosity of a bin folder (unless you GAC’d it which means you have 2 pages in your GAC just dedicated to EL assemblies). EL, if it was collapsed into 1 assembly wouldn’t be that big (take make a couple seconds longer to download over dial-up on a one-click deployed application) or take any noticeably longer time to load, plus you wouldn’t have to ensure other assemblies are loaded also just to use it. They created there own monster and I don’t even want to use the thing anymore.

    Now look at CSLA – one of the easiest, most flexible and scalable architectures I’ve used in the past 9 years – 2 assemblies. BTW…it’s extremely performant (if you’ve never used it) I got load tests that just fly when I use it (may because it doesn’t require a billion pointer to run).

    The lesson here is that flexibility and scalability is a factor of design, not implementation!

    My rule of thumb has always been, if I haven’t had or have had some have a VALID reason to use the assembly as a separate item in the past 6 months, I never will, and I collapse it into something else and remove the complexity.

    Thanks for the validation. As always…an awesome post.

  • Patrick Smacchia

    >They created there own monster and I don’t even want to use the thing anymore.

    And notice the irony, they are the ones who provide architecture guidance :o )

  • Brian

    NO KIDDING!

  • http://castalia.wordpress.com Ira

    Do you know what a pain posts like this are?

    I am now sat merging several libraries into the one library where the previously split dll’s are separated by namespace instead.

    Do you know how much fun it is repairing 100′s of errors where a ribbon control referenced loads of images in the previous .dll.

    God it is boring.

  • http://www.robertbeal.com Robert Beal

    Awesome article, just what I was looking for. Am effectively the build master at Huddle now. I’ve been trying to reduce build time, and have done many things, but now am at the point where I need to reduce some of the 60 projects we have.

    It was a good read. I’ve yet to use NDepend fully, but that’s next on my list.

  • http://www.4444444444444444409hcvw0wvhm9p8hemc.com Preved

    Nice conversation here. http://111111111tv0m0vttqay-7vt-vt0-mqva.com iemctg 222222 [url=http://33333333333333333sfgwet.com]333333[/url]:)

  • http://ondevelopment.blogspot.com Patrik Hägne

    I would add one valid reason to create an assembly. The cases when you want to use a different language to implement some features, such as VB.Net or F#.

  • Marcel Sorger

    @Mark Nijhof
    Lots of times interfaces will be used in multiple applications like Test and production or in WCF on the client and server.
    Besides that I always thought compiling interfaces was quite fast should make little difference.

  • Ade

    I think you’ll find the latest release of EL actually has less assemblies.