【问题标题】:DDD, Repository, & EncapsulationDDD、存储库和封装
【发布时间】:2010-11-11 17:24:38
【问题描述】:

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

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

下面是一个简单的示例:在我们的目录应用程序中,部件可以是包含许多其他部件的包。因此,Part POCO 有一个返回 IEnumerable 的“GetChildren()”方法是有意义的。它甚至可能在列表退出时做其他事情。

但是该列表是如何解决的呢?似乎存储库就是答案:

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 使用该部件,而不是相反。

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

【问题讨论】:

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


    【解决方案1】:

    解决此问题的一种方法是将逻辑移动到子部分本身 - 即更改类的语义,以便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)属性 - 它只是不知道如何自己填充它。

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

    【讨论】:

    • 有趣。但我对“将 GetChildren(part) 移入 IPartService 并将其从 part 中删除”以及“Part 类仍然具有 Childparts 属性”感到困惑。如果部件出于某种原因需要按摩它的孩子怎么办?
    • 如果在您从存储库中检索它时需要立即进行按摩,我会将这个逻辑放在IPartService.GetChildren() 中。如果您需要能够在任意时间修改子部件,您可以创建另一个服务方法,例如 IPartService.UpdateChildPartPrices(Part part).(或两者 - 您可以从 GetChildren 调用 UpdateChildPartPrices。)
    • 感谢您抽出宝贵时间,杰夫!
    • 我希望你能得到更多的答案,这是一个令人烦恼的,有趣的问题。我已经更新了我的答案,以说明原始建议的争议性(并承认其责任)。
    【解决方案2】:

    这里缺少的部分是Parts 对象的行为,以及您希望如何使用聚合。您是否需要对每个 Part 的单个子级进行第 n 次递归,或者您是否只使用“根”Part(即没有父级的那些)并且它是一个整体的子级?

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-11
      • 1970-01-01
      • 1970-01-01
      • 2013-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多