Domain driven design DDD、存储库和;封装

Domain driven design DDD、存储库和;封装,domain-driven-design,repository-pattern,poco,Domain Driven Design,Repository Pattern,Poco,如果有人认为这是被打死的,我提前道歉。我刚刚花了几个小时在这里搜索和阅读了很多优秀的帖子,但我仍然感到困惑 我的困惑源于DTO与DDD和存储库。我希望我的POCO域对象具有smarts,并希望从存储库中获取它们。但似乎我必须违反一些封装规则才能使其正常工作,而且似乎这会将DTO的头转到他们的头上 这里有一个简单的例子:在我们的目录应用程序中,一个部件可以是包含许多其他部件的包。因此,Part POCO有一个返回IEnumerable的“GetChildren()”方法是有意义的。它甚至可能会做一

如果有人认为这是被打死的,我提前道歉。我刚刚花了几个小时在这里搜索和阅读了很多优秀的帖子,但我仍然感到困惑

我的困惑源于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);
    }
}