Domain driven design DDD、存储库和;封装
如果有人认为这是被打死的,我提前道歉。我刚刚花了几个小时在这里搜索和阅读了很多优秀的帖子,但我仍然感到困惑 我的困惑源于DTO与DDD和存储库。我希望我的POCO域对象具有smarts,并希望从存储库中获取它们。但似乎我必须违反一些封装规则才能使其正常工作,而且似乎这会将DTO的头转到他们的头上 这里有一个简单的例子:在我们的目录应用程序中,一个部件可以是包含许多其他部件的包。因此,Part POCO有一个返回IEnumerableDomain driven design DDD、存储库和;封装,domain-driven-design,repository-pattern,poco,Domain Driven Design,Repository Pattern,Poco,如果有人认为这是被打死的,我提前道歉。我刚刚花了几个小时在这里搜索和阅读了很多优秀的帖子,但我仍然感到困惑 我的困惑源于DTO与DDD和存储库。我希望我的POCO域对象具有smarts,并希望从存储库中获取它们。但似乎我必须违反一些封装规则才能使其正常工作,而且似乎这会将DTO的头转到他们的头上 这里有一个简单的例子:在我们的目录应用程序中,一个部件可以是包含许多其他部件的包。因此,Part POCO有一个返回IEnumerable的“GetChildren()”方法是有意义的。它甚至可能会做一
interface IPartRepository : IRepository<Part>
{
// Part LoadByID(int id); comes from IRepository<Part>
IEnumerable<Part> GetChildren(Part part);
}
接口IPartRepository:IRepository
{
//Part LoadByID(int id);来自IRepository
IEnumerable GetChildren(部分);
}
及
类零件
{
...
公共IEnumerable GetChildren()
{
//可能会在退出时操纵此列表!
返回partRepository.GetChildren(this);
}
}
因此,现在我的目录的使用者除了(正确地)从存储库加载部件外,还可以通过直接调用GetChildren(Part)绕过某些部件封装的逻辑。那不是很糟糕吗
我读到存储库应该提供POCO,但DTO有利于在“层间”传输数据。许多零件属性都是计算出来的——例如,价格是根据复杂的定价规则计算出来的。价格甚至不会出现在来自存储库的DTO中——因此,似乎将定价数据传递回web服务需要DTO消费该部件,而不是反过来
这已经太长了。我的头拧到哪里去了?解决这个问题的一种方法是将逻辑移到子部分本身中——也就是说,更改类的语义,以便
部分
对象在与父对象关联时负责转换自身
例如,如果零件
的价格取决于其父零件
,则可在以下时间确定价格(至少):
- 构建时,如果父级
(以及所有其他必要数据)可用零件
- 在
方法中或响应事件,即AttachToParent(partparentpart)
OnAttachedToParent(partparentpart)
- 客户代码需要时(即首次访问其
属性时)Price
编辑:我最初的答案(如下)真的不符合DDD的精神。它涉及到使域对象成为简单的容器,许多人认为这是一种反模式的设计(请参阅)
部分
和IPartRepository
(我称之为IPartService
)之间的附加层解决了这个问题:将GetChildren(Part)
移动到IPartService
中,并将其从部分
中删除,然后进行客户端代码调用IPartService
,以获取Part
对象及其子对象,而不是直接访问存储库。Part
类仍然有一个ChildParts
(或Children
)属性-它只是不知道如何填充它本身
显然,这会带来额外的成本-如果在大多数情况下不需要额外的业务逻辑,那么您可能会为存储库调用编写或生成大量传递代码。这里缺少的部分是
Parts
对象的行为,以及您希望如何使用聚合。您是否需要处理每个部分
到第n个递归的单个子项,或者您是否只处理“根”部分
(即没有父项的部分)及其作为一个整体的子项
拥有一个包含相当一般类型的部分作为子项的聚合根,似乎不能很好地表达您的域模型,但您可以这样做并递归地延迟加载每个子集合。然而,我仍然会非常小心无限递归是可能的
至于您的第二个问题,DTO不是用于在层之间传输数据,而是用于在应用程序层内外传输数据
如果您使用的是面向服务的体系结构(您提到了web服务,但它可以是任何SOA),那么它们都非常有用。您的服务将查询您的存储库,执行任何额外的工作,然后将您的域对象映射到平面DTO以发送回请求的客户端。DTO应该简单,不包含特定于序列化目的的逻辑和应用程序功能
在应用程序内部使用域对象,在应用程序外部使用DTO。有趣。但我对“将GetChildren(part)移动到IPartService并将其从part中删除”和“part类仍然具有Childparts属性”感到困惑。如果part出于某种原因需要对其子级进行按摩怎么办?如果从存储库检索时需要立即进行按摩,我会将该逻辑放在IPartService.GetChildren()
中。如果您需要能够在任意时间修改子部件,您可以使用另一种服务方法,例如IPartService.UpdateChildPartPrices(部件部件)。
(或者两者都可以-您可以从GetChildren
调用UpdateChildPartPrices
),谢谢您花时间,杰夫!我希望你能得到更多的答案,这是一个令人烦恼、有趣的问题。我更新了我的答案,以解释原始建议的争议性(并承认其责任)。
class Part
{
...
public IEnumerable<Part> GetChildren()
{
// Might manipulate this list on the way out!
return partRepository.GetChildren(this);
}
}