DDD Repository:使用DAO进行分离?

Posted

技术标签:

【中文标题】DDD Repository:使用DAO进行分离?【英文标题】:DDD Repository: Use DAO for separation? 【发布时间】:2015-03-24 03:16:24 【问题描述】:

关于Repositories、它们在 *** 和整个网络上的使用和布局存在争议性的讨论。我对如何有效地实现存储库后面的数据访问抽象(例如数据库)感到困惑。

我没有使用 ORM 工具/框架,因为我想亲自查看细节。目前我正在使用 DAO 对象来访问(mysql)数据库并提供业务对象(域对象)。由数据库表中的外键给出的关联在相应对象的 DAO 中解析和加载(目前没有延迟加载)。由于我不想在业务逻辑中直接使用我的数据库 DAO,我认为存储库是一个很好的进一步抽象。在 Repository 中实现复杂查询(如 GetEmployeesByShopAndPosition())时,我遇到了困难:我看到了两种实现方法:

    蛮力:使用员工 DAO 并将 所有 员工作为业务对象(包括关联的商店/职位)从数据库加载到存储库的集合中。遍历集合并返回在给定 Shop 和 Position 工作的员工。 高效:实现一个数据库查询,连接相关表并通过 EmployeeDAO 中的 where 子句返回只需要的员工。

第一种方法使用存储库实际上应该具有的集合性质,但似乎效率很低。第二种方法生成bloated DAO,但效率更高。

我的问题:

    这里更喜欢什么或在实践中是如何完成的? 我错了,Repository 不应该与 DAO 一起使用,数据库相关代码可以直接放在 Repository 中吗? 作为一个处理聚合的存储库,它是否应该实际组装相关联的外键来构建(完整)业务对象,而不是我当前使用的 DAO?

我知道这个主题不是非黑即白,因为所涉及的设计模式也可以以不同的方式实现,但我想有一些准则不应该被打破或混淆,以分离关注点和持久性无知 (PI) .

【问题讨论】:

虽然我理解避免使用 ORM 工具/框架的愿望,但我确实认为您不查看他们的文档和示例会使事情变得更加困难。 php 的 Doctrine 2 ORM (doctrine-orm.readthedocs.org/en/latest/tutorials/…) 很容易理解,应该可以回答您的大部分问题。 【参考方案1】:

你实际上在这里问了很多问题,所以我会尽量让答案尽可能简洁:)

存储库返回 Aggregate RootEntity。有些人坚持认为存储库只返回 AR,这很好,而且总是足够的。

有两种类型的存储库(Vaughn Vernon 在他的实施领域驱动设计一书中很好地描述了):

以收藏为导向 面向持久性

您可能会遇到并更频繁地使用面向持久性的方法。这可能是混乱出现的地方w.r.t。道。当然,DAO 可能会返回一个业务对象,但它可能会返回更多。

您的 查询 示例是 DAO 可能更合适的地方。因此,在领域驱动设计领域,您会经常遇到 CQRS(命令/查询职责分离)。归结为查询您的域。

您应该有一个薄的、专用的查询层,它以最合适的格式(但不是实体)返回结果。在 c# 中,我使用 DataTableDataRowstring 之类的东西,如果需要,有时还会使用复杂的 DTO。

存储库只关心 AR,例如:

获取 添加 移除

存储库基本上使用各种逻辑 DAO(ADO.NET、ORM --- 我尽量避免使用 ORM)。

关于使用关联外键检索 AR 的第二点:AR 应该从不包含对另一个 AR 的引用。实体和Value Objects 很好。对于关联的 AR,可以使用 ID 或值对象来表示外部 AR。 AR 本身可能包含一个复杂的结构,但不要将所有权与收容混淆。 OrderLine 包含在 Order 中。 Customer 拥有 Order。所以Order 将有一个OrderLine 集合,但不会引用Customer 对象(而是ID/VO)。

Order/OrderLine 示例说明了我们不查询域的原因。当我们想要一个给定开始日期和结束日期之间的订单列表时,我们可能对所有订单数据不感兴趣,当然也不对订单行感兴趣。所以加载这些聚合没有意义。这就是在查询域时,像延迟加载这样令人讨厌的事情的地方。恕我直言,延迟加载不应该存在 :) --- 一个简单的查询层就足够了。

希望对您有所帮助。

【讨论】:

这里有很多有趣的点,谢谢。这里使用的实体和业务对象是同义词吗?我尝试创建一个丰富的域模型,并希望使用 proper 对象而不是高性能的查询原始数据,但我认为这是低效的,不是目标。我尝试在存储库中构建业务对象,并解析并加载了所有引用(外键)。不查询域对我来说似乎是个好方法。 实体和业务对象/领域对象确实是同义词。尝试使用所有相关引用构建 AR 总是会导致痛苦——这是一个很好的迹象,表明可能有问题。 @EbenRoux 很好的答案。在您看来,该查询层将位于何处?您是通过应用层使用边界 dto 访问它还是让它与 ddd 应用结构完全分离? @SneakyPeet:谢谢 :) --- 从逻辑上讲,就像存储库接口存在于域中,然后会有基础设施实现,查询“层”可能是独立于它自己的基础设施实施。然而,在物理上,我倾向于将所有这些(存储库/查询)放在一个程序集中(例如 Company.BoundedContext.dll)。如果我真的需要将其分开,那么我会这样做。在同一个程序集中创建它们时,您只需了解逻辑分离,以便在需要时进行拆分。【参考方案2】:

    尽可能使用数据库的强大功能。这就是 ORM 会尝试做的事情——在 SQL 查询中尽可能多地进行过滤、排序等。

    我认为同时拥有存储库和 DAO 没有什么价值。它们都抽象出持久存储。如果你不想使用 ORM,你通常会在具体的 Repository 实现中处理数据库查询生成部分。但在协作应用程序中,这也需要实现其他复杂的东西,例如变更跟踪、事务管理等。

    在整个聚合的意义上是完整的,是的。但作为一种良好的建模实践,尽量不要在聚合中存储(并因此再水化)对其他聚合根的引用。

这是从“经典”DDD 的角度来看,但如果您想走 CQRS 路线(有人会说这是唯一明智的路线;),请务必遵循 @EbenRoux 的建议。

【讨论】:

我有方法使用数据库功能进行查询,并结合大量脚本向客户端提供数据,这种方法运行速度很快,但我没有结构和关注点分离。因此,我为每个实体创建了一个从数据库中获取数据的 DAO,但是在特定 DAO 中执行连接查询时我陷入了困境。将要连接的表中的数据放在哪里?直接使用 foreign DAO 创建实体?从查询的数据构造业务对象以使用域模型,但速度明显较慢?我想我对 Business Objects 上瘾了 :) DDD 的存储库不是到业务对象(实体)的一对一映射,它们重新水合整个聚合,即其中包含多个实体的集群。这是 IMO DAO 和存储库之间的主要区别。也许你应该更多地研究聚合的​​概念,这一切都会有意义。 是的,我订购了 Vaughn Vernon 的 DDD 书,以深入挖掘存储库/聚合的概念。

以上是关于DDD Repository:使用DAO进行分离?的主要内容,如果未能解决你的问题,请参考以下文章

DDD+SOA的事件驱动微服务读写分离架构,读后随笔

领域驱动设计(DDD)实践之路(第二篇)

如何在 DDD 中使用 EF DbContext 分离我的聚合

DAO 设计模式

阿里技术专家详解DDD系列 第三讲 - Repository模式

阿里技术专家详解DDD系列 第三讲 - Repository模式