背景 :
为了我自己的清晰/自我教育,我正在尝试使用 TDD + DDD 实现一个简单的订单输入应用程序。我的主要目标是通过分离关注点来保持架构清洁。
我有四层(现在)......
此外,我想在关键层之间传递 POCO DTO ......特别是在 Persistence=>Domain 层和 ApplicationFacade=>Presentation 层之间。所以,我有 CustomerDto、OrderDto、OrderItemDto 以及在共享包中定义的适当关系。
我想使用构造函数注入(inject)将 ICustomerRepository 的实现注入(inject)到客户“业务实体”类中,然后在“业务实体”上调用 Customer.Save() 以启动创建/更新过程,最终调用 Save 方法客户存储库。毕竟,客户是“聚合根”,拥有保存所需的所有信息……它也是注入(inject)的客户存储库的“守护者”。
问题:
这是我遇到障碍的地方。我希望保持域/BLL 层尽可能纯净,并避免将其与任何第三方框架和 API 耦合, 但是 Customer.Save() 方法需要将客户“聚合根”及其所有 Orders 和 OrderItems 转换为它们的 DTO 版本,以便传输到注入(inject)的持久层 CustomerRepository ......这是 Automapper 的工作。
问题是......如果我不把Automapper放在Domain/BLL层,我不太确定 在哪里 它应该去。
即使它的工作是编排,将它放在 ApplicationFacade 中也感觉不对。
把它放在 Domain/BLL 层肯定感觉不对,因为我想保持它的原始状态。
因此,我觉得我错过了一些东西……我在接近这个问题时对工作部分应该如何组合在一起来完成这个任务有一个根本的误解。有什么建议么? (请温柔一点,我对这一切都很陌生,对 SO 也很陌生。让我知道是否需要展示我目前所拥有的一些代码。)
请您参考如下方法:
为了回答您的具体问题,我将更一般地谈论您的架构。您为项目设计的架构与 DDD 使用的典型架构略有不同。虽然划分职责的方式很典型,但在 DDD 中,域类不负责它们自己的持久性。事实上,DDD 的口头禅是 persistence ignorance .这基本上意味着域类对持久性无知——它们没有 Save
方法。相反,应用程序服务(架构中的应用程序外观层)与存储库协调以重构和持久化域实体。
接下来,在实现存储库时,通常不需要在底层持久性技术和域类之间建立显式 DTO。使用 NHibernate 和 EF 等 ORM,域类和关系表之间的映射可以用映射类或基于 XML 的配置来表示。结果,您的域类被隐式映射 - 不需要 DTO。有些情况确实需要 DTO,在这种情况下,DTO 和域类之间的映射应该由存储库实现封装。这是您可以调用 AutoMapper 的地方。
最后,使用 DTO 在表示层和应用程序/域层之间进行通信是一种常见的做法。要准确确定应该在哪里进行映射,您必须深入挖掘最适合您的项目的架构。大多数现代 DDD 架构都基于 Hexagonal architecture .在六边形架构中,您的域位于中心,所有其他“层”使域适应特定技术。例如,可以将存储库视为 adapter域和特定的数据库技术之间。 ASP.NET WebAPI 可以看作是域和 HTTP/REST 之间的适配器。同样,表示层可以看作是领域和特定领域之间的适配器。 DTO 可以在这些适配器中的每一个中体现,并且适配器负责映射到这些 DTO 和从这些 DTO 映射。
一个典型的例子是使用应用程序服务在您的域上建立一个外观。还没有 DTO 在起作用。接下来,使用 ASP.NET WebAPI 创建一个服务层(DDD 中的开放主机服务) - 一个适配器。您创建 ASP.NET WebAPI 特定的 DTO,并由该适配器映射到这些 DTO 和从这些 DTO 映射。因此,如果您使用 AutoMapper,则应在该层调用它。这可以显式完成,也可以在 aspect-oriented 中完成。使用 Action 过滤器的方式。表示层也是如此。表示层可以直接耦合到应用程序服务或 ASP.NET WebAPI 开放主机服务。在任何一种情况下,表示层都有责任在域类(来自应用程序服务或来自 WebAPI 的 DTO)与其自己的原语(如 ViewModel)之间进行映射。