10

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

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

下面是一个简单的示例:在我们的目录应用程序中,一个部件可以是一个包含许多其他部件的包。因此,Part POCO 有一个返回 IEnumerable<Part> 的“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 使用该部件,而不是相反。

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

4

2 回答 2

2

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

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

  • 在构建时,如果父级Part(和所有其他必要的数据)可用。

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

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


编辑:我的原始答案(如下)确实不符合 DDD 的精神。它涉及使域对象成为简单的容器,许多人认为这种设计是一种反模式(参见贫血域模型)。

Part和之间的附加层IPartRepository(我称之为IPartService)解决了这个问题:移入GetChildren(part)IPartService从中删除它Part,然后调用客户端代码IPartService来获取Part对象及其子对象,而不是直接访问存储库。该类Part仍然有一个ChildParts(or Children) 属性——它只是不知道如何自己填充它。

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

于 2009-07-17T14:52:45.900 回答
0

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

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

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

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

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

于 2009-08-04T15:45:59.737 回答