Hi Brian,
I'm an Architect, so I usually do things visually. I would create a
"component view" diagram (I have some background in RUP) that illustrates
the dependencies between "components" (where a component is a logical
grouping of classes that defines a particular functionality or hides a
particular area of complexity). These are static dependencies: in other
words, Item A is dependent on Item B if some code within Item A refers
specifically to a class, type, or method defined in Item B.
A good article from Scott Ambler is here:
http://www.agilemodeling.com/artifacts/componentDiagram.htm
(In fact, I strongly recommend this entire site.)
If you have a diagram like this, you will see some things. If your design
is solid, you will see "natural clusters" of components that tend to align
with either (a) functional groupings within your systems, or (b) the
organizational structure of your company. (The latter is unfortunate, but
is typical of teams that collaborate by copying code, rather than creating
common designs). (Note: if you do not see natural clusters of components,
you have created a design according to the antipattern "Big Ball of Mud,"
and will need to seriously embark upon some efforts to identify natural
clusters and break the unnecessary links that bind them).
Now that you can see your natural groupings, take a totally different
ingredient, and add it to the mix: add the Layers pattern (described by
Buschmann, further described by Microsoft's Patterns and Practices group).
This pattern has you break out "libraries" where a library is a collection
of components that can be identified on a GRID. (Your description makes it
clear that you have considered the functionality of your system in terms of
vertical slices. I would also encourage you to define horizontal slices as
well, to make a coherent grid).
One of the natural requirements of this grid: dependencies may only travel
in one direction (usually up, depending on how you draw your diagram). In
other words, an item may depend only on the items that are defined in layers
below it... never beside it, and never above it.
When you apply your naturally occuring clusters of components to this grid,
you will see that some of the classes that you thought should align together
in a single library should not. That in fact, some of the classes belong in
an upper layer, while others, due to their nature, belong in a lower layer.
This is especially true if those lower layer capabilities are reused (or you
would like for them to be reused) by other upper layer classes. This
activity forces you to consider ways to reduce your coupling. My earlier
response, about coding to interfaces, is one of the refactoring techniques
that accomplishes this goal.
So, how does this horizontal slicing work?
In my diagrams, the lowest slice are the classes that hide the complexities
of accessing a system function, external package, or network protocol. This
includes database access, connection to the active directory, reading and
writing queues (MSMQ, et. al.), file access, config file access, sending
e-mail, connecting to a WebDAV directory, etc. Another, completely
independent set of low level classes is a set of Base classes and
Interfaces. These define the objects that the rest of my system will use to
communicate and organize information.
The next layer up depends on both of these packages, and is involved with
using the low level services to create and manipulate objects that meet the
base class definitions. This is the layer where some folks use tools, but I
have yet to find a tool that creates these classes in a way that makes much
sense to me. These are the objects that hold the data that will move
through the services. This is the "model" layer often described in the
Model-View-Controller pattern.
The next layer up is the "controller" layer. (See descriptions of the
Model-View-Controller pattern). This layer contains classes that
encapsulate the business functionality. (e.g. in your financial system, you
are probably doing double-entry accounting. These classes are aware of how
to balance your entries before committing them, to insure that total credits
equals total debits).
The next layer up is an "interface services layer" that groups the
controllers and some visible data objects as services. This layer
encapsulates and hides the vast majority of the classes below it, and
reduces complexity to a series of messages. If you want to pay a bill, you
send a message described as "object A" to "Service B" and the system will
take care of finding the bill, cutting the check, and balancing the books.
The next layer up is the User Interface service layer. In a rich client
app, this layer is nearly always coded in the same executable as the client
windows themselves. In a web app, this layer is nearly always coded as
code-behinds under the web pages. This layer takes the data from the user
interface and composes the messages required by the interface services
layer. It takes the responses and fills user interface controls or makes
the data visible. This aligns with the classic definition of the "view"
classes in the MVC pattern.
The topmost layer are the windows or web pages themselves. This is nearly
always thin "visual" work that creates the layout of controls on a form or
window, manages tab order, responds to short-cuts, handles resizing of the
window, etc. I spend as little time in this layer as I can, because I'm not
gifted in this area, and others clearly are.
As you can see, this is considerably more detailed than is typical of older
descriptions of Model-View-Controller, but is both consistent with it, while
still allowing for newer Service Oriented patterns to interact with the
systems architecture.
With a layering design like this one, you can define packages that will
minimize the effort of your team both during compilation and during
maintenance. Your system sounds complex, which tells me that it is an
Enterprise class system. By defining packages in this manner, you can
isolate areas of likely business rule changes, and keep your system usable
while reducing code (and therefore, reducing bugs). It should also be much
easier to create automated unit tests for the system because the functions
of a layer do not depend on the layer above, and the layer below can be
modeled with mock objects. (Google on NUnit and NMock).
Now that you have a package design, you can create solutions that make
sense. I would create assemblies (projects) that align with the component
libraries that emerge from the above process. I would define solutions to
include the necessary projects to make a single developer or small team
effective. Note that a project can occur in multiple solutions (although I
would minimize this). I've seen enterprise class systems with only five or
six solutions, and perhaps 40 or so projects. I would avoid having a
solution that makes the source code available for more than two layers at a
time... that's just a rule of thumb to help me to stay focussed on reducing
coupling and increasing cohesion.
I have not normally seen solutions that contain only one project. That is
not unheard of, but it is somewhat rare, and usually done when there are
issues with deployment that drive this (An event handler in Sharepoint, for
example, is normally signed and placed into the GAC... the difficulty of
deploying multiple dependent libraries into the GAC often yeilds a single
large assembly. As a result, the solution file will contain only the single
assembly and perhaps some resources and an installer).
I hope this helps to answer your questions.
--- Nick Malik