我正在设置一个应用洋葱体系结构和DDD模式的解决方案。
DDD的一项原则鼓励域实体只有私有的setter和私有的默认构造函数,以确保您不能以无效状态创建域实体。
存储库包含对域实体的数据操作,这些域操作是从数据库映射到数据库的。我一直在尝试以下两种方法:
域实体一个纯粹的方式:没有默认的构造,没有公开设置器;验证在构造(多个)进行;这样可以确保您不能以无效状态创建域实体。副作用是,在读取操作中很难在存储库中取消实现它们;当您需要反射以便能够创建实例和映射属性时;以及在Dapper请求中使用动态功能,需要将其映射到实际的域实体。如果我将其直接映射到域实体而不使用动力学,则 Dapper抛出一个异常,即没有公共构造函数。
域实体在一个非纯粹的方式:你让一个默认的构造函数,所有的setter方法是公开的;因此您可以创建在给定时间点无效的实体。在这种情况下,你需要手动调用validate()方法,以确保它们是有效的,然后再继续。这样就使存储库中的去饱和化变得更加容易,因为您不需要反射或动态操作即可将数据库映射到模型。
这两种方法都可以使用,但是,使用选项2时,存储库变得更加简单,因为它们包含的定制映射代码少得多,并且在没有反射的情况下显然也将具有更高的性能。当然,DDD并不是以纯粹的方式应用的。
在决定我将在项目中使用什么之前,先问一个问题:是否存在其他任何可以处理私有构造函数和设置方法的 micro-ORM框架,以便将数据库映射到那些是否支持“纯”域实体,而没有其他自定义映射逻辑? (没有EF或NHibernate,我想要轻量级的东西)。
或者其它technial解决方案,以方便储存库映射保持“纯”模型实体的方法中的组合?
编辑:我实现的解决方案如下。
首先,域实体中的构造函数和设置器都是“内部”的,这意味着它们不能由域模型的使用者设置。但是,我使用'InternalsVisibleTo'允许数据访问层直接访问它们,因此这意味着使用Dapper可以非常轻松地从数据库中取消实现(不需要中间模型)。从应用层,我只能使用域的方法来直接改变域实体,而不是性能。
第二,为了从我的应用程序层构造新的domein实体,我添加了流畅的构建器来帮助构建域实体,因此现在可以像这样构建它们:
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"))
当构建器“构建”实体时,验证也将完成,因此您永远无法创建处于无效状态的实体。
答案 0 :(得分:2)
DDD的一项原则鼓励域实体只有私有的setter和私有的默认构造函数,以确保您不能以无效状态创建域实体。
那不是很正确。是的,富域模型通常不公开setter,但这是因为它们不需要 setter。您可以告诉模型在更高的抽象层次上要做什么,并允许它确定应如何修改其自己的数据结构。
类似地,在通常情况下,公开默认构造函数很有意义:如果您将聚合视为有限状态机,则默认构造函数是在以下位置初始化聚合的一种方式:其“开始”状态。
因此,通常,您可以通过以下两种方式之一重新构建聚合:以默认状态初始化聚合,然后向其发送一堆消息,或者使用Factory
模式,如蓝皮书所述
这意味着它们之间需要额外的映射,这会使代码更加复杂
也许可以,但是这也可以确保您的域代码较少依赖ORM魔术。特别是,这意味着您的域逻辑可以在不同数据结构上运行,而不是用于使持久性“简单”的数据结构。
但这不是免费的-您必须在代码中描述如何从聚合根中获取值并返回数据库(或作为数据库的代理的ORM实体)。
答案 1 :(得分:1)
关键是您不使用Dapper处理域实体,而是在POCO实体的存储库层中使用它。您的存储库方法将通过将POCO实体(Dapper用于数据访问)转换为Domain实体来返回Domain实体。