域驱动设计和跨域交互


10

我是DDD的相对新手,但是我正在阅读任何东西,只要我能动手实践并提炼出我的知识即可。

我遇到了这个DDD问题,答案之一吸引了我。

DDD有界的上下文和域?

在一个答案中,张贴者给出了一个电子商务系统的示例,其中产品至少在两个域中:

1)产品目录2)库存管理

好的,这一切都有道理,即在您的电子商务前端中,您对显示产品信息感兴趣,而对库存管理不感兴趣。

但。您可能要在网页上显示库存水平,或者要显示库存的版本号(假设库存是书籍,杂志等)。此信息来自库存域。

那么,您将如何处理呢?你会

a)加载产品域和库存域聚合?b)您是否会在“产品”域实体上保留一些属性,以用于库存数量和库存版本,然后在“库存”实体更新时使用“域事件”来更新这些属性?

最后一个问题。我知道我们注定要忘记/忽略域的持久性,而只需考虑域。但是,请仔细考虑一下,在上面的示例中,我们最终可能会有2个DB表用于产品目录和产品库存。现在,我们在这些产品中使用相同的标识符吗?或者,我们可以对数据使用1个表行还是1个表行,并简单地将相关数据映射到聚合属性上?

Answers:


8

您可能要在网页上显示库存水平,或者要显示库存的版本号(假设库存是书籍,杂志等)。此信息来自库存域。

此时要注意的主要事情是您正在谈论一个视图,这就是说使用陈旧数据是可以接受的。

话虽如此,您不需要与聚合(用于防止更改违反业务不变性)进行交互,而只需使用聚合状态的最新副本即可。

因此,我通常希望对产品目录运行查询,对库存运行查询,然后将两者组合到支持视图的DTO中。

是否同时加载产品域和库存域聚合?

这样就近了。我们不需要加载聚合,因为我们不会更改任何内容。但是我们需要他们的状态。所以我们可以加载它。也就是说,我通常希望这两个域在不同的进程中运行。因此,我们将两者都调用,而不是同时加载。

您是否会在“产品”域实体上保留一些属性,以用于库存数量和库存版本,然后在更新库存实体时使用“域事件”来更新这些属性?

“不要越过溪流。那将是不好的。”

使用事件跨领域上下文协调信息:主意。将属于一个领域的概念推向另一个领域:一个好主意的反面,但更多的则相反。

您想保持域干净。与域交互的应用程序并不重要。因此,例如,库存应用程序调用产品应用程序中的服务以查询某些特定于产品的概念以添加到视图中是合理的。或相反亦然。

我不知道将单个应用程序限制为单个域的任何原因。只要有一个真实的来源,您就可以按照自己喜欢的任何方式分发交易。

但是,请仔细考虑一下,在上面的示例中,我们最终可能会有2个DB表用于产品目录和产品库存。现在,我们在这些产品中使用相同的标识符吗?

那将是简单的方法。大体而言,您使用相同的标识符,因为现实世界中的实体是相同的;两个不同限界上下文的模型是不同的实体,但该模型是不是真实世界的实体。

如果这不起作用,那么您将需要一些查询来弥合差距。我认为最常见的变化是,较新的实体保留了较旧实体的ID。您还将在一个BC中看到这一点:申请人一旦获得批准,便成为客户。这是一个不同的汇总(与客户关联的状态与申请人的状态具有不同的不变性);因此,如果您的持久层使用事件流,则新聚合的流将需要其他标识符。因此,在某处会有一些状态说“此申请人成为此客户”。

或者,我们可以对数据使用1个表行还是1个表行,并将相关数据简单地映射到聚合属性上?

赞! 不,不要那样做。您正在添加事务争用而没有任何业务原因。


我已将其打勾作为答案,还要感谢下面的@guillaume,他也指出,读取要在视图中显示的数据不需要加载汇总。感谢您提供这么长的详细答案。来自“传统”数据模型的第一种方法,我发现很难强迫自己忘记持久层并专注于领域语言。就在我认为自己明白了的时候,我读了另一篇文章,这使我的理解大为不同。
PendorPaul '16

我刚刚读了这篇文章msdn.microsoft.com/en-us/magazine/dn802601.aspx,其中详细介绍了使用域事件在上下文之间复制某些数据的方法。该示例在客户服务和订单处理系统之间共享客户列表。这是在订单系统中复制客户数据,并使用事件来同步数据。面对我们在这里回答的问题,这是不正确的。当然,在链接的示例中,订单上下文仅需要一个customerID,并且可以从可以访问客户服务上下文的应用程序中填充该ID。
PendorPaul '16

您将在这里想得很精确。在系统之间处理数据与在域模型之间复制数据不同。每当您看到“只读[嘟嘟]”时,就很明显地表明数据不属于该域(即使应用程序可能仍在乎它)。
VoiceOfUnreason

是的,那也是我的想法。如果没有“专家”发表混乱的文章,思想过程的转变就足够困难了。今天,我已经花了很多时间来分析自己的域,以尝试并完全理解我的BC和“聚合根”所在的位置。我几乎在那儿,但是我也知道我可以随时改进和重构模型。几天来,我一直陷于过度分析的境地,担心开始编写代码,担心我心中没有DDD。我认为最好继续破解并继续质疑我的代码以及该模型是否正确。我会到达那里。谢谢
PendorPaul '16

也许我读错了,但是我相信您所说的做法被称为事件进行状态转移(Event-Carried State Transfer),它可以是非常强大的模型,尤其是对于需要过滤和页面数据的仪表板/表视图跨服务。您可以从域事件构建“投影”(基本上是视图)来驱动这些仪表板。我曾经很成功地使用过它。在正确的情况下,它可能是一个非常强大的工具。恕我直言,这里更重要的是,您意识到这些预测不代表领域模型,并且不应包含业务逻辑。
约旦

3

我认为您的问题确实需要2组正交的选项-

  • 您是加载两个对象并一起显示其数据,还是加载1个包含所需内容的对象?

  • 您是否使用聚合来显示内容或其他内容?

如果您相信CQRS方法,那么事实证明聚合可能不是读取的最佳选择。每次加载聚合时,无论是显示其数据还是对其进行修改,都将向系统添加并发和争用。此外,与使用为显示量身定制的即席读取模型相比,聚合可能更庞大且加载速度更慢。

来自您的Q的解决方案a)似乎有很多陷阱。选项b)可能是有效的,但仅当InventoryManagement需要使来自BC的数据在对Product聚合进行突变时强制不变量时才使用它。最好是聚合包含修改后检查其业务规则所需的所有数据,但在读取方面,它们可以位于任何地方。

关于数据,通常的建议是为“绑定上下文”提供自己的数据库(出于可部署性和SoC的原因)。如果要匹配两个BC之间的产品,可能必须使用相同的标识符。

关于跨BC的交互,您可能还想看看/programming/16713041/communication-between-two-bounded-contexts-in-ddd


1
好,那有帮助。本质上,我们使用聚合根和BC来确保我们的不变量是一致的,并且我们的数据有效,并且要执行的操作被包装在聚合或BC中。在读取和显示该数据时,我们可以单独处理这些数据,并且重量轻,而不会使所有骨料/ BC水合。毕竟,当我们正在做的全部工作是在报表或屏幕上显示数据时,为什么还要加载汇总。这是很合理的。谢谢。
PendorPaul '16

这正是CQRS真正发挥作用的地方。您可以执行SQL查询,联接表并返回针对特定视图的定制查询。它还可以从查询方法混乱中清除您的存储库。此外,您甚至可以在服务(例如ElasticSearch)上复制数据并对其进行查询。
donotmatter

1

DDD适用于业务逻辑复杂的应用程序。“打印一些东西”不是复杂的业务逻辑。实际上,这根本不是业务逻辑。

如果一个上下文中的业务逻辑需要一些信息来正确处理某些用例,那么该信息就是该上下文的一部分。因此,一个有界上下文可能需要在不同的有界上下文中可用的信息的想法是没有意义的,因为有界上下文具有所需的所有信息。


好的,以Amazon为例,这是一个具有复杂业务逻辑的复杂系统。他们具有目录管理和库存管理。他们不需要总库存来管理目录,我的意思是产品名称,描述,类型,条件等。但是,它们确实在商店首页上显示了库存数量。在那种情况下,您想将“产品目录管理”域和“产品库存”域分开,但是需要在产品页面上显示一些有关库存的信息,您该怎么做?
PendorPaul

我想我想说的是“产品目录”域具有管理产品目录所需的所有信息。库存域具有管理库存所需的所有信息。但是,当我想向用户显示一些信息并且该数据来自2个域时,该在哪里进行操作。我是否只需在UI中加载两个域并绑定要显示的属性?还是我有某种报告/读取机制,可以返回包含我的UI所需数据的匿名类型或DTO?
PendorPaul '16

回想11个月前我的这些旧评论,这很可笑,但似乎是一辈子。您对读写方面完全满意。随着我的理解的发展,我正在处理的应用程序已经发展了很多。我现在通过Jimmy Bogards Mediatr使用CQRS方法。具有与聚合交互的命令,但是随后使用查询和查询处理程序来带回我需要显示的内容的绝对灵活性是不可思议的。将其包装到调用这些QueryHandlers的View中,这种分离非常好。谢谢
PendorPaul 2016年

@PendorPaul,我想我是11个月前(现在是13个月前)的地方。是什么把你带到了现在的位置?
格雷格·贝尔

1
@GregBell您需要强制改变观念。我陷入了“设计数据库,建立数据层,建立一些业务逻辑...等”的方法。我专注于创建所有包含的实体。例如,电子商务网站中的“产品”,它处理了从定价,描述,库存,库存位置到……的所有内容,但这变得极其复杂。受限上下文方法意味着在库存上下文中,“产品”仅包含用于库存管理的信息和行为。描述和图像在内容上下文中管理,定价在定价上下文中。
PendorPaul

1

从我的角度来看,对“产品”有不同的定义-每个边界上下文都有其自己的“产品”域定义:

  • 在内容管理边界上下文中,产品具有图像和描述文本。
  • 在“库存边界”上下文中,产品具有库存数量,产品卖方,预测该产品何时可用
  • 在价格计算边界上下文中,有规则规定每件产品可能要花费多少。

在这些之上,我将添加一个附加的Shop-Bounding-Context及其自己的产品定义(其他Bounding-Contexts的产品域的相关组合)。

商店产品的内容和可用性中的“库存”中将包含“图像和描述文字”,而库存中的“产品卖方”中将没有。

此额外的Shop-Bounding-Context取决于Bounding-Context-s的内容,库存,价格


那么,您如何创建该Shop-Product BC?在这种情况下,您是否具有对产品和库存BC的引用,并且是否在加载存储产品BC时从持久性存储中添加它们,然后通过StoreProduct属性提供那些BC中所需的属性?我发现这篇文章实际上符合我的想法,跨越了BC事件,但是上面的@ guillaume13指出,出于显示目的,我可以避免BC,而只需拉回我所需的数据即可。msdn.microsoft.com/zh-CN/magazine/dn802601.aspx
PendorPaul,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.