开发者

Should I mix technologies within assemblies?

I have a medium-sized project, which implements about 20 or so different concepts. At the beginning, I chose to organize my assemblies based on conceptual layers, like so:

MyProject.Domain.dll (References System.Data.Linq, etc.)
  \ConceptA\
  \ConceptB\
  \ConceptC\
  \...\

MyProject.Presentation.dll
  \ConceptA\
  \开发者_JS百科ConceptB\
  \ConceptC\
  \...\

MyProject.WinForms.dll (References System.Windows.Forms, etc.)
  \ConceptA\
  \ConceptB\
  \ConceptC\
  \...\

MyProject.App.exe (References all the above)

I've recently read in a DDD book that I should group my assemblies based on the domain concept it represents, rather than the technology layer, like so:

MyProject.ConceptA.dll (References System.Data.Linq, System.Windows.Forms, etc.)
  \Domain\
  \Presentation\
  \WinForms\

MyProject.ConceptB.dll
  \Domain\
  \Presentation\
  \WinForms\

MyProject.ConceptC.dll
  \Domain\
  \Presentation\
  \WinForms\

MyProject.App.exe (References all the above)

I don't have enough experience to judge the two approaches in the long term. I want to strike the best balance between complexity and flexibility. I have a few concerns that make me feel ambivalent:

  • Grouping by concept makes it easier to find my code, since it's all in one place.
  • Grouping by technology makes sure I don't call MessageBox.Show from my domain layer.
  • I will eventually switch out the data access and presentation technologies.
  • At the end of the day, all the assemblies will be referenced by the main application anyway.
  • When grouped by concept, where will the tests go? Won't I have to put them in separate assemblies so they don't ship with the program?

In your experience, which is the best approach?


TL;DR: You should be doing both, but don't split your project up into multiple assemblies just for the sake of it. By splitting your assemblies down to reusable components you will end up using a combination of both methods where appropriate.


Firstly, I'd say that depending on the size of your project there may not be any need to separate either concepts or layers our into separate assemblies - the advantages of separating out your code into separate assemblies is twofold:

  1. Allowing other assemblies / applications to make use of your code by referencing your assembly
  2. Reducing the size of large assemblies by splitting into many smaller assemblies

If you don't have any need for either of those two (and you wont in the future) then keep your life simple and just bung everything into one assembly.


Secondly, the primary reason for separating code out into separate assemblies is to re-use that code - for example if you have a piece of processing logic being used in a Windows Forms application, separating that out into a separate assembly lets you re-use that logic in say a console or web application. For this reason I usually find that the best way is to separate on concepts, for example:

Company.Project.Concept.dll

Where "Concept" is something that you want to re-use, be it a set of common Windows controls or some data access logic.

Note that when re-using a concept its fairly rare to want to re-use all conceptual layers of that concept (Domain / Presentation / WinForms). Normally either your concept only consists of 1 layer (e.g. some form of processing), or when re-using that concept you are only interested in 1 or possibly 2 layers. In this case if your "Concept" assemblies also contain other extra logic (such as WinForms) you are just referencing extra code that will never be used. For this reason its also normal to separate out into conceptual layers if you have them, for example:

Company.Project.Concept.Processing.dll
Company.Project.Concept.WinForms.dll

I.e. in the example you gave I'm advocating that if anything you would want 9 assemblies, not 3:

MyProject.ConceptA.Domain.dll
MyProject.ConceptA.Presentation.dll
MyProject.ConceptA.WinForms.dll

Of course splitting your project down into hundereds of assemblies is completely pointless unless these individual concepts are actually going to be used elsewhere which brings me back to my first point - don't bother splitting up assemblies unless you actually need to, or to put it another way split your assemblies up into the re-usable components that make sense:

  • If nobody else is going to use your Windows Forms controls then don't bother to split them into a separate assembly
  • If most people are going to use ConceptB and ConceptA together then combine them into a single assembly
  • If most people are going to want to use the Domain and Presentation layers together, combine them into a single assembly

As a working example I automatically opt to split smaller projects into two assemblies - the application itself which contains all the "presentation" (be it Web, WinForms, WPF or a Console app), and another assembly which contains the "meat" of the application - the underlying functionality being exposed by your application (e.g. image processing, data retrieval etc...). This helps if I ever want to expose the same functionality in a different style of application.

Once more though I would err on the side of too few assemblies rather than too many - its easier to split an assembly in two than it is to combine two assemblies back into one. Unless you can find a compelling reason to split an assembly / project up into multiple assemblies, don't bother.


I usually opt for a mix with a heavier lean towards the first approach (grouping by layer), but that doesn't mean that it's the best. My reasoning is that usually most of the operations, even across concepts, are going to share functionality of some kind, and this allows me to have helper classes/structures/etc to share internally across all of the different concepts.

Of course, that can be applied the other way as well, if all of your assemblies are grouped by concept then you can have classes/structures/etc specific to the concept as opposed to the layer.

In the end, it's about what do you feel will gain the most reuse. In my current project, I have everything split by layer, but each layer has a specific task (there are discoverers, enrichers, stagers) that each have separate domains (different websites to apply these tasks to) and layers (some fetch data in the representation native to the domain, then there's the data layer, etc).

In regards to switching out the data access and presentation technologies, I suggest you adopt a dependency injection/inversion of control approach which forces you to abstract those pieces of functionality (interfaces/abstract base classes) which you can swap out whenever you want.

For example, my data access layer, I have two interfaces, one for the repository pattern (the reader) and one for the unit of work pattern (the writer, and it's adapted, I don't always have a collection of objects, sometimes my commit method takes a single instance and only allows operations on one instance at a time).

These interfaces expose nothing about data access technologies; the underlying store can be a text file for all I care; it just exposes the means to get the data that I want and write that data back. I can switch from ADO.NET to LINQ-to-SQL to LINQ-to-Entities (all of which I have done) at my desire, depending on the need.

As for testing, I test on the same level as my assemblies, if I have an assembly:

Casper.Discovery.dll

With the following domains and tasks underneath:

Casper.Discovery.dll
    DomainA
        Models
        Data
    DomainB
        Models
        Data

Then I'll have a testing assembly:

 Casper.Testing.Discovery.dll

With the same directory/file structure for tests underneath. I do this specifically so I can group the testing assemblies into a solution folder labeled "testing". While they all order correctly even if Testing was placed at the end, putting it after my general vanity namespace qualifier allows for easy perusal of the list without being confused about Testing at the end messing up what I expect to see if I was looking at its counterpart (the thing being tested).


It is not about grouping by conceptual layers only nor by domain concepts, think first of why do you group or separate assemblies:

  • Maintainability: To separate the concerns and logically group related concepts, you group by aspect in terms of Domain, Data Access, Infrastructure, Distributed Services, maintaining a separation of concerns in terms of layer abstraction. another aspect of maintainability is to separate bounded contexts (business concepts) if your domain logic is large and complex thus you may have nested grouping by bounded context.

  • Re-usability: To separate the components that likely to be re-used in other contexts or projects

  • Dynamic replace of components: To separate the components that may be replaced at runtime/deploy time, like the logging framework, the data access layer, ...

the end result is that you mostly will need to have a nested (multi level) of grouping when you are developing a medium to large complex domain solution:

example is to group by bounded context then by layer abstraction:

  • Company.CRM.Domain
  • Company.CRM.Repositories.NHibernate
  • Company.CRM.Repositories.EF
  • Company.CRM.Application
  • Company.Trading.Domain
  • Company.Trading.Repositories.NHibernate
  • Company.Trading.Application
  • Company.Shared.Infrastructure
  • Company.Shared.Infrastructure.Log4NetLogger
  • Company.Shared.Infrastructure.EnterpriseLibraryLogger
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜