DDD 原则和 Dapper 存储库

L-F*_*our 8 c# architecture domain-driven-design dapper

我正在建立一个应用洋葱架构和 DDD 模式的解决方案。

DDD 原则之一鼓励域实体仅具有私有 setter 和私有默认构造函数,以确保您无法创建处于无效状态的域实体。

存储库包含域实体上的数据操作,这些实体从数据库映射到数据库。我一直在尝试以下两种方法:

  1. 纯粹的领域实体:没有默认构造函数,没有公共设置器;验证在构造函数中完成;这确保您无法创建处于无效状态的域实体。副作用是在读取操作中更难在存储库中对它们进行非物质化;因为您需要反射才能创建实例和映射属性;以及在需要映射到实际域实体的 Dapper 请求中使用动态。如果我将其直接映射到域实体而不使用动态,Dapper 会抛出一个异常,即没有公共构造函数

  2. 以非纯粹的方式实现域实体:允许使用默认构造函数,并且所有 setter 都是公共的;因此您可以创建在给定时间点无效的实体。在这种情况下,您需要手动调用 Validate() 方法,以确保它们有效,然后再继续。这使得存储库中的去物质化变得更加容易,因为您不需要反射或动态来将数据库映射到模型。

然而,这两种方法都有效,使用选项 2 存储库变得更加简单,因为它们包含的自定义映射代码要少得多,而且如果没有反射,显然性能也会更高。当然,DDD 并不是以纯粹的方式应用的。

在决定我将在项目中使用什么之前,有一个问题:是否有任何其他微 ORM 框架可以处理私有构造函数和设置器,以便支持将数据库映射到那些“纯”域实体,而无需额外的操作自定义映射逻辑?(没有 EF 也没有 NHibernate,我想要一些轻量级的东西)。

或者其他技术解决方案来保持“纯”模型实体方法与简单的存储库映射相结合?

编辑:我实施的解决方案如下。

首先,领域实体中的构造函数和设置器都是“内部的”,这意味着它们不能由领域模型的使用者设置。但是,我使用“InternalsVisibleTo”来允许数据访问层直接访问它们,因此这意味着使用 Dapper 从数据库中进行非物质化非常容易(不需要中间模型)。从应用程序层,我只能使用域方法来更改域实体,而不能直接更改属性。

其次,为了从应用程序层构建新的域实体,我添加了流畅的构建器来帮助构建域实体,因此我现在可以像这样构建它们:

 User user = new UserBuilder()
        .WithSubjectId("045454857451245")
        .WithDisplayName("Bobby Vinton")
        .WithLinkedAccount("Facebook", la => la.WithProviderSubjectId("1548787788877").WithEmailAddress("bobby1@gmail.com"))
        .WithLinkedAccount("Microsoft", la => la.WithProviderSubjectId("54546545646").WithEmailAddress("bobby2@gmail.com"))
Run Code Online (Sandbox Code Playgroud)

当构建器“构建”实体时,验证也会完成,因此您永远无法创建处于无效状态的实体。

Voi*_*son 4

DDD 原则之一鼓励域实体仅具有私有 setter 和私有默认构造函数,以确保您无法创建处于无效状态的域实体。

这不太正确。是的,丰富的域模型通常不会公开 setter,但那是因为它们不需要setter。您告诉模型在更高的抽象级别上做什么,并允许它确定应如何修改自己的数据结构。

同样,在很多情况下,公开默认构造函数是有意义的:如果您将聚合视为有限状态机,那么默认构造函数就是将聚合初始化为“启动”状态的一种方法。

因此,通常您可以通过以下两种方式之一重新构建聚合:要么将其初始化为默认状态,然后向其发送一堆消息,要么使用Factory蓝皮书中描述的模式。

这意味着之间有额外的映射,这使得代码更加复杂

也许吧,但它也确保您的域代码更少依赖于 ORM 魔法。特别是,这意味着您的域逻辑可以在与用于使持久性变得“容易”的数据结构不同的数据结构上运行。

但它不是免费的——您必须在代码中描述如何从聚合根中获取值并返回到数据库(或返回到 ORM 实体,充当数据库的代理)。