【问题标题】:Should I mix technologies within assemblies?我应该在程序集中混合技术吗?
【发布时间】:2011-10-12 10:41:28
【问题描述】:

我有一个中型项目,它实现了大约 20 个不同的概念。一开始,我选择根据概念层来组织我的程序集,如下所示:

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

MyProject.Presentation.dll
  \ConceptA\
  \ConceptB\
  \ConceptC\
  \...\

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

MyProject.App.exe (References all the above)

我最近在一本 DDD 书籍中读到,我应该根据它所代表的域概念而不是技术层对我的程序集进行分组,如下所示:

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)

从长远来看,我没有足够的经验来判断这两种方法。我想在复杂性和灵活性之间取得最佳平衡。我有一些让我感到矛盾的担忧:

  • 按概念分组更容易找到我的代码,因为它都在一个地方。
  • 按技术分组确保我不会从我的域层调用MessageBox.Show
  • 我最终会关闭数据访问和表示技术。
  • 最终,所有程序集都将被主应用程序引用。
  • 按概念分组时,测试将在哪里进行?我不是必须将它们放在单独的程序集中,这样它们就不会随程序一起提供吗?

根据您的经验,哪种方法最好?

【问题讨论】:

    标签: .net assemblies domain-driven-design project-organization


    【解决方案1】:

    TL;DR: 你应该两者都做,但不要为了它而将你的项目分成多个程序集。通过将程序集拆分为可重用组件,您最终将在适当的情况下使用这两种方法的组合。


    首先,我想说的是,根据您项目的大小,可能不需要将我们的概念或层分离到单独的程序集中 - 将您的代码分离到单独的程序集中的优点是双重的:

    1. 允许其他程序集/应用程序通过引用您的程序集来使用您的代码
    2. 通过拆分成许多较小的组件来减小大型组件的大小

    如果您不需要这两者中的任何一个(将来也不会),那么请让您的生活保持简单,只需将所有内容整合到一个组件中即可。


    其次,将代码分离到单独的程序集中的主要原因是重用该代码 - 例如,如果您在 Windows 窗体应用程序中使用了一段处理逻辑,则将其分离输出到一个单独的程序集中可以让您在控制台或 Web 应用程序中重新使用该逻辑。出于这个原因,我通常发现最好的方法是在概念上进行分离,例如:

    Company.Project.Concept.dll
    

    “概念”是您想要重复使用的东西,可以是一组常见的 Windows 控件或一些数据访问逻辑。

    请注意,当重用一个概念时,想要重用该概念的所有概念层(域/演示/WinForms)是相当罕见的。通常,您的概念仅包含 1 层(例如某种形式的处理),或者在重新使用该概念时您只对 1 层或可能 2 层感兴趣。在这种情况下,如果您的“概念”程序集还包含其他额外的逻辑(例如 WinForms),那么您只是在引用永远不会使用的额外代码。出于这个原因,如果你有概念层,它很正常,例如:

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

    即在您给出的示例中,我主张如果您需要 9 个程序集,而不是 3 个:

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

    当然,将您的项目拆分为数百个程序集是完全没有意义的,除非这些单独的概念实际上将在其他地方使用,这让我回到了我的第一点——除非您确实需要,否则不要打扰拆分程序集,或者换句话说,将您的程序集拆分为有意义的可重用组件

    • 如果没有其他人打算使用您的 Windows 窗体控件,则不要费心将它们拆分为单独的程序集
    • 如果大多数人要同时使用 ConceptB 和 ConceptA,那么请将它们组合成一个程序集
    • 如果大多数人都想同时使用域和表示层,请将它们组合成一个程序集

    作为一个工作示例,我自动选择将较小的项目拆分为两个程序集——应用程序本身包含所有“演示文稿”(无论是 Web、WinForms、WPF 还是控制台应用程序),另一个程序集包含“肉" 应用程序的 - 应用程序公开的底层功能(例如图像处理、数据检索等......)。如果我想在不同风格的应用程序中公开相同的功能,这会有所帮助。

    再一次,尽管我宁愿选择太少而不是太多的组件 - 将一个组件一分为二比将两个组件重新组合成一个更容易。 除非您能找到令人信服的理由将程序集/项目拆分为多个程序集,否则不要打扰。

    【讨论】:

    • 感谢您富有洞察力的回答。很难确定是否要重用某些东西,因为没有定义明确的策略(这本身可能是一个更大的错误!)。唯一的计划是几年后可能会变成一个 webapp,所以我认为每层分离更有意义,它确实给了我验证的好处,即我没有在我做跨层调用的地方不应该。
    【解决方案2】:

    我通常会选择更倾向于第一种方法(按层分组)的组合,但这并不意味着它是最好的。我的推理是,通常大多数操作,即使是跨概念的,都会共享某种功能,这允许我有帮助类/结构/等在内部共享所有不同的概念。

    当然,这也可以以其他方式应用,如果您的所有程序集都按概念分组,那么您可以拥有特定于 概念 的类/结构/等,而不是层.

    归根结底,关键在于您认为什么会获得最多的重用。在我当前的项目中,我将所有内容按层拆分,但每一层都有一个特定的任务(有发现者、丰富者、阶段者),每个任务都有单独的域(应用这些任务的不同网站)和层(一些在域的本地表示,然后是数据层等)。

    关于切换数据访问和表示技术,我建议您采用dependency injection/inversion of control 方法,它强制您抽象那些您可以随时更换的功能(接口/抽象基类)你想要的。

    例如,我的数据访问层,我有两个接口,一个用于repository pattern(读取器),一个用于unit of work pattern(写入器,并且它已适应,我并不总是有一个集合对象,有时我的提交方法需要一个实例,并且一次只允许对一个实例进行操作)。

    这些接口没有公开任何数据访问技术;底层存储可以是我所关心的文本文件;它只是公开了获取我想要的数据并将该数据写回的方法。我可以根据需要从ADO.NET 切换到LINQ-to-SQL 再到LINQ-to-Entities(所有这些我已经完成)。

    至于测试,如果我有一个程序集,我会在与我的程序集相同的级别上进行测试:

    Casper.Discovery.dll
    

    下面有以下领域和任务:

    Casper.Discovery.dll
        DomainA
            Models
            Data
        DomainB
            Models
            Data
    

    然后我将有一个测试程序集:

     Casper.Testing.Discovery.dll
    

    下面的测试具有相同的目录/文件结构。我专门这样做,以便可以将测试程序集分组到标有“测试”的解决方案文件夹中。虽然即使 Testing 放在最后,它们都正确排序,但将它放在我的一般虚荣命名空间限定符之后可以轻松阅读列表,而不会在最后混淆 Testing正在看它的对应物(正在测试的东西)。

    【讨论】:

    • 谢谢。我确实使用 DI 原则来隔离我的层 - 所有这些最终都通过 .exe 级别的注入组件组合在一起。我的疑问主要是当我切换到 EF 时,装配级分离是否会有所帮助。如果所有数据访问类都放在自己的程序集中,或者分散在各个概念的程序集中,会不会更容易。
    • @IliaJerebtsov:我真的不认为是这样。因为您正在抽象出数据访问的细节(或者应该是),所以您永远不会从抽象中得到特定的 LINQ-to-* 内容。通过这种方式,它为您提供了混合搭配的终极多功能性,以满足您在任何级别的需求。
    【解决方案3】:

    这不仅仅是按概念层或领域概念进行分组,首先要考虑为什么要对程序集进行分组或分离:

    • 可维护性:为了分离关注点并对相关概念进行逻辑分组,您可以根据域、数据访问、基础架构、分布式服务按方面进行分组,在层抽象方面保持关注点分离。可维护性的另一个方面是,如果您的领域逻辑庞大且复杂,那么您可能会根据有界上下文进行嵌套分组。

    • 可重用性:将可能在其他上下文或项目中重用的组件分开

    • 组件的动态替换:分离在运行/部署时可能被替换的组件,如日志框架、数据访问层……

    最终结果是,当您开发中型到大型复杂域解决方案时,您大多需要嵌套(多级)分组:

    示例是先按有界上下文分组,然后按层抽象:

    • 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

    【讨论】:

      猜你喜欢
      • 2019-04-20
      • 1970-01-01
      • 1970-01-01
      • 2015-07-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多