it-swarm.cn

在Web应用程序中DDD聚合真的是一个好主意吗?

我将深入研究领域驱动设计,而我遇到的一些概念从表面上看很有意义,但是当我更多地考虑它们时,我不得不怀疑这是否真的是一个好主意。

例如,聚合的概念很有意义。您可以创建较小的所有权域,因此不必处理整个域模型。

但是,当我在Web应用程序上下文中考虑此问题时,我们经常访问数据库以拉回小的数据子集。例如,页面可能仅列出订单数量,并带有单击链接以打开订单并查看其订单ID的链接。

如果我正确理解了Aggregates,我通常会使用存储库模式返回一个OrderAggregate,其中将包含成员GetAllGetByIDDeleteSave。好的,听起来不错。但...

如果我调用GetAll列出我所有的订单,那么在我看来,这种模式将需要返回整个汇总信息列表,完整的订单,订单行等...当我只需要该信息的一小部分时(仅标头信息)。

我想念什么吗?还是在这里使用某种程度的优化?我无法想象有人会主张在不需要时返回全部信息。

当然,可以在您的存储库上创建GetOrderHeaders之类的方法,但这似乎违背了使用诸如存储库之类的模式的目的。

谁能为我澄清一下?

编辑:

经过大量研究后,我认为这里的脱节之处在于,纯存储库模式与大多数人认为的存储库模式不同。

Fowler将存储库定义为使用集合语义的数据存储,通常存储在内存中。这意味着创建整个对象图。

Evans修改了存储库以包括聚合根,因此截取了存储库以仅支持聚合中的对象。

大多数人似乎将存储库视为美化的数据访问对象,您可以在其中创建获取所需数据的方法。正如Fowler的企业应用程序架构模式中所描述的那样,这似乎并不是目的。

还有一些人认为存储库是一个简单的抽象,主要用于简化测试和模拟,或将持久性与系统的其余部分分离。

我猜答案是,这是一个比我最初想象的要复杂得多的概念。

43
Erik Funkenbusch

不要使用域模型和聚合进行查询。

实际上,您要问的是一个足够普遍的问题,因此已经建立了一套避免这种情况的原则和模式。它称为 [〜#〜] cqrs [〜#〜]

31
quentin-starin

我在如何最好地使用域驱动设计中的存储库模式方面感到挣扎,但仍在挣扎。在第一次使用它之后,我想到了以下做法:

  1. 存储库应该很简单;它仅负责存储和检索域对象。所有其他逻辑应在其他对象中,例如工厂和域服务。

  2. 存储库的行为就像一个集合,就好像它是集合根的内存中集合一样。

  3. 存储库不是通用DAO,每个存储库都有其唯一且狭窄的接口。存储库通常具有特定的Finder方法,使您可以按域搜索集合(例如:给我所有用户X的未结订单)。存储库本身可以在通用DAO的帮助下实现。

  4. 理想情况下,Finder方法将仅返回聚合根。如果这样做效率低下,它还可以返回仅包含您所需要的值的只读值对象(如果这些值对象也可以用域表示,则为加号)。最后,存储库还可以用于返回聚合根的子集或子集的集合。

  5. 诸如此类的选择取决于所使用的技术,因为您需要找到一种使用所使用的技术最有效地表达域模型的方法。

8
Kdeveloper

我认为您的GetOrderHeaders方法根本不会破坏存储库的目的。

DDD(除其他事项外)与确保通过聚集根(例如,您没有OrderDetailsRepository)来获取所需内容有关,但它并没有限制您所提到的方式。

如果OrderHeader是Domain概念,则应按此定义它,并具有适当的存储库方法来检索它们。只要确保您要通过正确的聚合根即可。

6
Eric King

我对DDD的使用可能不被认为是“纯粹的” DDD,但是我已经对DB数据存储使用了DDD来适应以下现实世界的策略。

  • 聚合根具有关联的存储库
  • 关联的存储库仅由该聚合根使用(它不是公开可用的)
  • 存储库可以包含查询调用(例如,GetAllActiveOrders,GetOrderItemsForOrder)
  • 服务公开了存储库的公共子集和其他非压缩操作(例如,从一个银行帐户向另一个银行帐户转帐资金,LoadById,搜索/查找,CreateEntity等)。
  • 我使用Root-> Service-> Repository stack。 DDD服务仅假定用于实体无法回答自己的任何事情(例如,LoadById,TransferMoneyFromAccountToAccount),但是在现实世界中,即使root应该能够自己“回答/执行”这些命令。请注意,授予实体访问另一个聚合根服务的权限没有错!但是,请记住,您将不包括在服务(GetOrderItemsForOrder)中,而是将其包括在存储库中,以便聚合根可以使用它。请注意,服务不应像存储库那样公开任何打开的查询。
  • 我通常在域模型中(通过接口)抽象定义存储库,并提供单独的具体实现。我在域模型中完全定义了服务,并注入了具体的存储库以供使用。

**您不必带回整个汇总。但是,如果您想要更多,则必须询问根目录,而不是其他服务或存储库。这是延迟加载,可以通过手动延迟加载(将适当的存储库/服务注入根目录)来手动完成,也可以使用和ORM支持。

在您的示例中,如果我想在单独的调用中加载详细信息,则可能会提供仅带订单标头的存储库调用。注意,通过拥有“ OrderHeader”,我们实际上是在域中引入了一个附加概念。

4
Mike Rowley

您的域模型以最纯粹的形式包含您的业务逻辑。支持业务运营的所有关系和运营。概念图中缺少的是 应用程序服务层 的概念,服务层环绕域模型,并提供了业务域的简化视图(如果可以的话,可以投影)可以根据需要更改域模型,而不会直接影响使用服务层的应用程序。

走得更远。聚合的想法是存在一个对象,即聚合根,负责维护聚合的一致性。在您的示例中,订单将负责操纵其订单行。

在您的示例中,服务层将公开诸如GetOrdersForCustomer之类的操作,该操作仅返回查看订单摘要列表所需的内容(您将其称为OrderHeaders)。

最后,存储库模式不只是一个集合,还允许声明式查询。在C#中,您可以将LINQ用作 查询对象 ,或者大多数其他O/RM也提供查询对象规范。

存储库在域和数据映射层之间进行中介,就像内存中的域对象集合一样。客户端对象以声明方式构造查询规范,然后将其提交给存储库以使其满意。 (来自 Fowler的存储库页面

看到您可以针对存储库创建查询,因此提供处理常见查询的便捷方法也很有意义。即如果只需要订单的标头,则可以创建一个仅返回标头的查询,并从存储库中的便捷方法公开它。

希望这有助于澄清问题。

3
Michael Brown

我知道这是一个古老的问题,但我似乎得出了不同的答案。

当我创建一个存储库时,通常会包装一些cached查询。

Fowler将存储库定义为使用集合语义的数据存储,通常存储在内存中。这意味着创建整个对象图。

将这些存储库保存在您的服务器内存中。它们不只是将对象传递给数据库!

如果我在带有列出订单页面的Web应用程序中,则可以单击以查看详细信息,我很可能希望我的订单列表页面具有有关订单的详细信息(ID,名称,金额,日期)帮助用户决定他们要看哪一个。

此时,您有两个选择。

  1. 您可以查询数据库,然后精确拉回列出清单所需的内容,然后再次查询以拉出需要在详细信息页面上看到的各个详细信息。

  2. 您可以进行1个查询,以拉回所有信息并将其缓存。在下一页请求中,您从服务器ram而不是数据库中读取。如果他的用户回击或选择了下一页,则您仍在零访问数据库。

实际上,如何实现它以及实现细节。如果我最大的用户有10个订单,则可能要选择选项2。如果我说的是10,000个订单,则需要选项1。在以上两种情况下,在其他许多情况下,我都希望存储库隐藏该实现细节。

展望未来,如果我能在订单列表页面上告诉用户在上个月他们在订单上花了多少钱(汇总数据),我是否愿意编写逻辑以在SQL中进行计算并再次往返于数据库,还是宁愿使用服务器内存中已经存在的数据进行计算呢?

以我的经验,聚合可以带来巨大的好处。

  • 它们是真正有效的重用部分代码。
  • 它们通过将业务逻辑正确地保留在核心层中,而不必钻取基础结构层来使sql服务器执行此操作,从而简化了代码。
  • 它们还可以减少所需的查询数量,从而极大地加快了您的响应速度,因为您可以轻松地缓存它们。
  • 我正在编写的SQL通常更易于维护,因为我经常只是询问所有内容并计算服务器端。
0
WhiteleyJ