如何建模存在于所有有界上下文中并且是应用程序核心部分的实体?

cfs*_*cfs 15 c# design-patterns domain-driven-design cqrs bounded-contexts

我正在使用DDD原则制作应用程序.在尽可能多地思考一切之后,我就开始制作我的有界背景了.我还没有设置最终结构,但截至目前,我的应用程序将包含以下有界上下文:

  1. 员工管理
  2. 购买
  3. 档案
  4. 报告

我希望它尽可能地可插拔,所以我可以单独开发和维护它们.他们可能会公开WCF或Web API来与它们进行交互.

我将使用Udi Dahans实现一个简单的CQRS模式.我不想一直使用事件源,消息总线等,因为这不是一个高度协作的应用程序(少于1000个用户,他们不太可能编辑相同的小数据集),这将增加了许多不必要的复杂性.

那么问题:

所有BC的员工和部门实体都很常见,如何建模?

部门是组织结构的一部分,因此在员工管理BC中,员工在一个部门工作,他们可以管理一个部门,他们有他们所从事过的部门的历史.

在购买BC商品是从部门购买,并交付给部门.Suplliers与不同部门签订了不同的合同.

在存档中,一些信息将被存档并绑定到一个部门,依此类推.

这同样适用于员工.

如何从有界上下文中保留数据?

它们可以映射到同一个数据库,也可以各自拥有自己的数据库.

到目前为止我已经做了一些想法

如何建模
我应该再建一个名为"公司"或"组织"的BC并在那里管理部门吗?

根据上面引用的Udi Dahans文章,我应该为每个BC制作一个部门实体和一个雇员实体,只需要我所需要的BC字段和行为.这听起来很合理,但后来我正在考虑如何实际使用它,我无法弄明白.我需要访问其他地方管理的部门,但我究竟是如何做到这一点而不是混合我的BC?

如何使用?
假设我通过查询从somwhere获取我的部门列表.在UI中我得到了我想要购买的部门列表.这是该部门的第一次购买,因此采购BC还不了解这个部门......所以购买BC的部门对象将填充从其他BC维护的数据 - 所以我该如何坚持这一点?如果不存在,我需要添加一些信息,如送货地址和发票地址?

在"注册部门UI"中,我应该在所有BC上调用"RegisterDepartment"服务,然后确保这些服务与通过UI(MVC控制器)所做的所有更改同步吗?

与员工一样.我想知道哪个员工进行了购买或在存档中放了一些东西.所以我不知何故在这些BC中也需要一个员工对象,但是从不同的BC管理它们.

持久化
可以通过将不同的employee-objects映射到数据库中的同一个表来解决上面的一些挑战.购买BC和Archive BC不能注册新员工,而是将信息附加到那些在那里的人并将他们绑定到同一数据库中的其他对象.然后数据库会让所有BC仍然生活在同一个世界......

我需要建议,所以我最终不会制作一些以后很难维护的东西.

小智 11

似乎你的大多数疑惑都围绕着:"不同的有界上下文如何共享单个现实生活对象?"

问题是,尽管实体是相同的,但每个BC都会对它们进行不同的处理.在员工管理BC中,整体权重集中在员工和部门实体上 - 您应该能够添加,修改,相互分配,保留历史记录并处理有关管理的所有业务逻辑.您可以实施一些保留员工个人数据,维护适当的官方结构或维护某些职责的政策.

另一方面,购买上下文中的部门实体仅意味着例如发票地址以及来自负责部门的人员,并且感兴趣的中心将构建订单.所有与购买程序没有直接关联的数据都应该提供给不同的背景.例如,如果域名要求必须将每个订单连接到部门并且缺少发票详细信息,则购买上下文不应尝试单独填写它们.相反,可以通知员工管理来填补缺失的部分.

请注意,它可能很好地发生在同一个应用程序甚至同一个窗口中.但是您必须确保它将通过Employee Management上下文发生,即通过调用上下文公共API.

作为附注,我不了解您的域名,但您可能想重新考虑您的上下文边界,例如将交付与购买分开.

继续使用并按照您的示例,如果您想进行购买,我会考虑遵循以下路径:

  • 阅读必要的部门数据(让我们等到以后再用"如何"),你可能想检查此时是否存在所有数据
  • 阅读可以购买的商品,具体取决于您的域名,可能值得引入其他BC,例如供应商.以上都是CQRS的"查询"部分
  • 构建订单或任何其他必要的购买上下文实体,执行验证或任何其他逻辑
  • 提交更改,保留购买上下文实体("命令"部分)
  • 创建和发布一些域事件(例如,通知存档或报告)

最后但同样重要的是,您不应该关心域层中的全局持久性.每个BC应该连接到一些数据访问或基础设施层,提供必要的对象并处理从哪里获取它们的详细信息.

特别是,实体不一定需要镜像数据库布局,并且是否存储在一个或多个数据库中的问题应该只是性能问题.例如,某些实体将引用相同的对象(例如员工名称),但可以从完全不同的表或数据库中获取其他详细信息(即购买历史记录或发送到存档的元素).您可以使用NHibernate之类的东西来轻松管理.


小智 5

我在这里回答一个老问题,但我有另一个 OPs 问题中面临的问题的例子。

我有一种情况,我正在处理制造公司内使用的应用程序。该公司设有销售、运营、生产、会计、技术支持部门。在所有这些部门中,都有一个客户的概念。因此,我很难弄清楚我如何在这些部门中拥有一个 Customer 实体(我已将其映射为我的绑定上下文)。

正是在一遍又一遍地思考这个问题时,有些东西被点击了,然后我想起了我在 Jimmy Bogard 的一篇博客文章中读到的评论,他在那里谈到了他花了数周的工作才完成的领域模型,因为他们对这个问题的研究越多,他们就越了解这个领域,并且能够得出一个优雅的设计。

当我不再考虑持久性并不再考虑这些 BC 中的客户时,我的清醒时刻就开始了。

我意识到销售 BC 需要了解客户,但他们需要非常具体的信息,例如谁是 MD、联系人列表、办公室列表、决策者是谁、谁是财务联系人等。

现在,Ops BC 负责在系统中提出订单,他们需要一个客户的概念。但是,他们不需要知道 MD 是谁,或联系人列表,以及谁关心客户拥有哪些办公室?Ops 只需要知道客户名称,在我们的例子中是 4 个字符的客户代码。当我考虑到这一点时,我什至不需要将此信息作为实体保存,它可能只是我的 Ops BC 中的一个值对象。但是如何将这些信息输入到我的 Ops BC 中呢?嗯,这真的很简单。BC 定义了一个接口,我的应用程序可以通过该接口与 Ops BC 进行交互。我的应用程序不知道 Ops BC 内部发生了什么,但它知道有一个 Order Aggregatr 根,并且它有一个 RaiseNewOrder 方法,它接受一个 CustomerValueObject 类型的参数。CustomerValueObject 由一个 4 字母代码和一个客户名称组成。因此,在我的应用程序 UI 中,我可以使用 Sales(我们称之为客户管理)BC 来获取客户列表以显示下拉列表。然后,当我处理操作的事务部分时,即用户发布新订单信息时,我使用 Ops BC 并将 customerVO 作为参数传递。

因此,我的 Ops BC 和 Sales BC 是独立的,并且是独立的,它们在内部具有确保域完整性所需的概念,并且我能够在我的 UI 中访问我需要的数据,以便操作员可以选择客户然后在 Ops BC 中执行操作。

我意识到销售团队的客户与运营团队的客户不同。对于运维人员来说,客户只是他们用来标记订单的东西。他们对那家公司的内部运作或有关它的任何信息没有任何兴趣。只要我在整个 BC 中为客户提供一个通用 ID,我就有一种方法可以将数据拉回我需要用于演示的 ID。例如,我可以使用 Ops BC 拉回客户 XXXX 的订单列表。我不需要通过客户聚合进入来做到这一点。

现在,把这个皱纹扔进去。销售人员有时需要了解客户下的订单,所以我是否需要开始在我的销售 BC 中复制信息?这就是我卡住的地方,但后来我意识到我在谈论销售人员,他们是 UI 的用户,在 UI 中,我可以拉回给定客户的订单列表。

我对 DDD 还是很陌生,但我意识到 DDD 就是与领域专家的对话,当你正确地进行这些对话时,你会意识到你认为存在于 BC 中的实体......不是。

我认为我们作为开发人员通过经验设置为非规范化数据,并考虑数据库结构,以及我们如何将这些 Customer 对象持久化回表......所以我们将实体视为一团东西,但域专家们不会以这种方式看待这些实体。

当我问“那么会计部门需要从这个订单中看到什么”之类的问题时,对我来说达成交易的评论来自相当脾气暴躁的运营总监,他的回应减去咒骂是“我不在乎什么他们想要,我只想要我想要的”。我认为这就是您需要看待 BC 的方式。

无论如何,我希望这个华夫饼对某人有所帮助。