Deciding how to physically partition up your application isn't a .NET specific topic, but I'll use the word "Assemblies" and use .NET terminology since we work largely in .NET.
What's the right number of assemblies? When do you split up functionality into another assembly? Should you use ILMerge to make a single über-assembly? Perhaps one class or one namespace should be inside each assembly?
Here's my thinking...organized very simply.
Robert Martin has an excellent PDF on Granularity, while focused on C++, the concepts aren't C++ specific. He has a section called "Designing with Packages" where he asks these questions, that we should ask ourselves when we start organizing an application. Substitute "packages" for "assemblies" if you like. He defines a package as a "releasable entity.":
1. What are the best partitioning criteria?2. What are the relationships that exist between packages, and what design principles govern their use?3. Should packages be designed before classes (Top down)? Or should classes bedesigned before packages (Bottom up)?4. How are packages physically represented? In C++? In the development environment?5. Once created, to what purpose will we put these packages?[Robert Martin's "Granularity"]
He then goes on to answer these questions. Do read the PDF, but here's some terse quotes along with my own commentary:
"The granule of reuse is the granule of release."
If you're creating a reusable library or framework, think about the developer who is "downstream" from you and how they will reuse your library.
"The classes in a package are reused together. If you reuse one of the classes in a package, you reuse them all."
Not only this, but you reuse all their dependencies. Consider what the developer will be creating with your library and if he/she needs to include other assemblies of yours to accomplish the goal. If they have to add 4 assemblies to implement 1 plugin, you might consider rethinking the number of assemblies in your deployment.
Consider the effect of changes on the user of your library. When you make a change to a class, perhaps one that the user/developer doesn't care about, do they need to distribute that newly versioned library anyway, receiving a change in a class they didn't use?
"The classes in a package should be closed together against the same kinds of changes."
Developers don't usually use/reuse a single class from your assembly, rather, they'll use a series of related classes, either in the same namespace or in a parent namespace. Consider this, and the guideline above when you decide what classes/namespaces go in what assembly.
Classes that are related usually change for related reasons and should be kept together in a related assembly, so that when change happens, it's contained in a reasonably sized physical package.
"The dependant structure between packages must be a directed acyclic graph."
Basically, avoid circular dependencies. Avoid leapfrogging and having a (stable) Common assembly dependant on a downstream assembly. Remember that stability measures how easily an assembly can change without affecting everyone else. Ralf Westphal has a great article on Dependency Structure using Lattix, an NDepend competitor. Patrick Smacchia, the NDepend Lead Developer, has a fine article on the evils of Dependency Cycles.
Ralf has some very pragmatic advice that I agree with:
"Distinguish between implementation and deployment. Split code into as many assemblies as seems necessary to get high testability, and productivity, and retain a good structure." [Ralf Westphal]
This might initially sound like a punt or a cop-out, but it's not. The number of assemblies you release will likely asymptotically approach one. It'll definitely be closer to one than to one-million. However, if you ever reach one, worry, and think seriously about how you got there.
Udi Dahan commented on Patrick's Guiding Principles of Software Development and said, quoting himself from elsewhere (emphasis mine):
In the end, you should see bunches of projects/dlls which go together, while between the bunches there is almost no dependence whatsoever. The boundaries between bunches will almost always be an interface project/dll. This will have the pleasant side-effect of enabling concurrent development of bunches with developers hardly ever stepping on each other’s toes. Actually, the number of projects in a given developer’s solution will probably decrease, since they no longer have to deal with all parts of the system.
In the end, you should see bunches of projects/dlls which go together, while between the bunches there is almost no dependence whatsoever. The boundaries between bunches will almost always be an interface project/dll.
This will have the pleasant side-effect of enabling concurrent development of bunches with developers hardly ever stepping on each other’s toes. Actually, the number of projects in a given developer’s solution will probably decrease, since they no longer have to deal with all parts of the system.
I would respectfully disagree with the bolded point and direct back to the principle that team structure should never dictate deployment structure. A good source control system along with pervasive communication as well as good task assignment should prevent "stepping on...toes." Sure, it's perhaps a side effect, but definitively not one worth mentioning or focusing on.
What's the conclusion? Consider these things when designing your deployment structure (that is, when deciding if something should be a new assembly or not):
What IS the right number of assemblies, Dear Reader?
Ads by The Lounge