DDD,Repository,&封装

时间:2009-07-17 14:05:37

标签: domain-driven-design repository-pattern poco

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

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

这是一个简单的例子:在我们的目录应用程序中,Part可以是包含许多其他部分的包。因此,Part POCO有一个'GetChildren()'方法,返回IEnumerable<部分>。它甚至可能会在列表中显示其他内容。

但该名单如何解决?似乎存储库就是答案:

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(部分)来绕过一些部分封装的逻辑。那不是很糟糕吗?

我读到存储库应该提供POCO,但DTO适用于在层之间传输数据。计算了许多零件属性 - 例如,价格是根据复杂的定价规则计算的。价格甚至不会来自存储库的DTO - 因此将价格数据传递回Web服务似乎需要DTO使用Part,而不是相反。

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

2 个答案:

答案 0 :(得分:2)

解决此问题的一种方法是将逻辑移动到子部件本身 - 也就是说,更改类的语义,以便Part个对象在与父级关联时负责转换自身。

例如,如果Part的价格取决于其父Part,则可以在以下时间(至少)确定价格:

  • 构建时,如果父Part(以及所有其他必要数据)可用。

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

  • 客户端代码需要时(即首次访问Price属性时)。


编辑:我原来的答案(下面)真的不符合DDD的精神。它涉及使域对象成为简单容器,许多人将其视为反模式(参见Anemic Domain Model)。

PartIPartRepository之间的其他图层(我称之为IPartService)解决了这个问题:将GetChildren(part)移至IPartService并将其删除从Part开始,然后使客户端代码调用IPartService来获取Part个对象及其子项,而不是直接访问存储库。 Part类仍然具有ChildParts(或Children)属性 - 它只是不知道如何填充它。

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

答案 1 :(得分:0)

此处等式中缺少的部分是Parts对象的行为,以及您希望如何使用聚合。您是否需要为每个Part到第n次递归的个别孩子工作,或者您是否只使用“根”Part(即那些没有父母的人)并且它是整个孩子?

拥有Part聚合根,其中包含一个通常类型为Parts的子列表,因为它们似乎不会特别好地表达您的域模型,但您可以这样做并递归地延迟加载每个儿童集合。但是,我仍然会非常小心无限递归。

至于您的第二个问题,DTO不是用于在层之间传输数据,而是用于将数据传入和传出应用层。

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

在您的应用程序中使用您的域对象,在外面使用DTO。