2009-07-17 97 views
10

如果人们认为这已被殴打致死,我很抱歉。我刚刚花了几个小时搜索和阅读许多优秀的帖子,但我仍然困惑。DDD,存储库和封装

我的困惑的来源是DTO与DDD和存储库。我希望我的POCO域对象具有智能,并且我想从存储库中获取它们。但是,似乎我必须违反一些封装规则才能使其发挥作用,并且似乎可以将DTO变成他们的头像。

下面是一个简单的例子:在我们的目录应用程序中,零件可能是一个包含许多其他零件的包。因此,Part POCO有一个'GetChildren()'方法是有意义的,该方法返回IEnumerable < Part>。它甚至可能在列表中列出其他东西。

但是该列表如何解决?好像仓库是答案:

interface IPartRepository : IRepository<Part> 
{ 
    // Part LoadByID(int id); comes from IRepository<Part> 
    IEnumerable<Part> GetChildren(Part part); 
} 

而且

class Part 
{ 
    ... 
    public IEnumerable<Part> GetChildren() 
    { 
     // Might manipulate this list on the way out! 
     return partRepository.GetChildren(this); 
    } 
} 

所以,现在我的目录,除了从存储库(正确地)装载部分的消费者,还可以绕过某些部分封装通过直接调用GetChildren(part)来实现逻辑。那不好吗?

我读到存储库应该提供POCO的,但是DTO的可以在层间传输数据。计算了很多零件属性 - 例如,价格是根据复杂的定价规则计算的。价格甚至不在存储库中的DTO中 - 因此,将定价数据传递回Web服务似乎需要DTO消耗该部分,而不是相反。

这已经太长了。我的头在哪里拧开?

回答

2

解决此问题的一种方法是将逻辑移入子部件本身 - 即更改类的语义,以便Part对象负责在与父对象关联时对其进行转换。

例如,如果一个Part的价格是依赖于它的父Part,可以在下列时间(至少)确定的价格:

  • 在结构中,如果父Part(和所有其他必要的数据)。

  • AttachToParent(Part parentPart)方法或响应事件,即,OnAttachedToParent(Part parentPart)

  • 当客户端代码需要时(即第一次访问其Price属性时)。


编辑:我原来的答复(见下文)真的没有在DDD的精神。它涉及到域对象的简单容器,许多人认为这是一种反模式(参见Anemic Domain Model)。

PartIPartRepository之间的附加层(我称之为IPartService)解决了这个问题:移动GetChildren(part)IPartServicePart删除它,然后让客户端代码调用IPartService得到Part对象和他们的孩子,而不是直接打开存储库。 Part类仍然有一个ChildParts(或Children)属性 - 它只是不知道如何填充它本身。

显然,这会产生额外的成本 - 如果您在大多数情况下不需要额外的业务逻辑,则最终可能会为存储库调用编写或生成大量传递代码。

+0

有趣。但是我感到困惑的是'将GetChildren(part)移动到IPartService中,并将其从部分中移除“,然后'Part类仍然有一个Childparts属性。如果部分因某种原因需要按摩其孩子会怎样? – n8wrl 2009-07-17 15:40:13

0

这里公式的缺失部分是Parts对象的行为,以及您想如何使用聚合。你是否需要针对每个Part到第n次递归的个别子女工作,或者你是否只使用“根”Part(即那些没有父母的),并且它是整个孩子?

具有含相当一般类型Parts儿童的名单似乎将不能表达你的域模型特别好,但你可以做到这一点,递归延迟加载每个孩子集合Part聚合根。但是,我仍然非常小心无限递归的可能性。关于你的第二个问题,DTO不能像在数据传入和传出应用层时那样在层间传输数据。

如果您使用的是面向服务的体系结构(您提到web服务,但它可以是任何SOA),它们非常有用。您的服务将查询您的存储库,执行任何额外的工作,然后将您的域对象映射到平面DTO中以发送回请求客户端。 DTO应该是简单的,不包含逻辑和应用程序功能,以便序列化。

在您的应用程序内部使用您的域对象,并在外部使用DTO。