如何对存在于所有有界上下文中的实体进行建模,这些实体是应用程序的核心部分



我正在使用DDD原理制作一个应用程序。在尽可能多地思考了所有事情之后,我开始制作我的"有界上下文"。我还没有设置最终的结构,但到目前为止,我的应用程序将由以下有界上下文组成:

  1. 员工管理
  2. 采购
  3. 存档
  4. 报告

我希望它尽可能地可插拔,这样我就可以分别开发和维护它们。他们可能会公开WCF或Web API来与他们交互。

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

那么问题来了:

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

部门是组织结构的一部分,因此在员工管理BC中,员工在一个部门工作,他们可以管理一个部门,并且他们有自己工作过的部门的历史。

在采购BC时,商品从一个部门购买,然后交付给一个部门。主管与不同部门签订了不同的合同。

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

这同样适用于员工。

如何持久化有界上下文中的数据

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

到目前为止我的一些想法

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

根据上面引用的Udi Dahans的文章,我应该为每个BC创建一个部门实体和一个员工实体,并为该BC提供我需要的字段和行为。这听起来很合理,但我正在考虑如何实际使用它,但我无法理解。我需要访问其他地方管理的部门,但我到底该如何做到这一点,而不是混合我的BC?

如何使用
假设我通过查询从somhwhere获得部门列表。在UI中,我得到了一个我想购买的部门列表。这是该部门的第一次采购,所以采购BC还不知道该部门。。。因此,采购BC中的部门对象将由另一个BC维护的数据填充——那么我如何坚持这一点呢?如果不存在,我需要添加一些信息,比如交货地址和发票地址?

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

员工也是如此。我想知道是哪个员工进行了购买或将某些东西放入了档案中。因此,不知何故,我也需要这些BC中的员工对象,但从不同的BC管理它们。

持久化
上述一些挑战可以通过将不同的员工对象映射到数据库中的同一个表来解决。采购BC和存档BC不能注册新员工,而是将信息附加到现有员工身上,并将他们与同一数据库中的其他对象联系起来。然后数据库会让所有不列颠哥伦比亚人仍然生活在同一个世界。。。

我需要建议,这样我就不会做出以后很难维护的东西。

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

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

另一方面,采购上下文中的部门实体仅意味着,例如,发票地址,可能是负责部门的人员,而利益中心将构建订单。所有与购买程序没有直接联系的数据都应提供给不同的上下文。例如,如果域要求每个订单都必须连接到部门,并且缺少发票详细信息,则采购上下文不应尝试自己填写这些信息。相反,可以向员工管理层发出通知,以填补缺失的部分。

请注意,它很可能发生在同一个应用程序甚至同一个窗口中。但您必须确保它将通过员工管理上下文发生,即通过调用上下文公共API。

顺便说一句,我不知道你的域名,但你可能想重新考虑你的上下文边界,例如将交付和购买分开。

继续使用并以您为例,如果您想进行购买,我会考虑以下路径:

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

最后但同样重要的是,您不应该关心域层中的全局持久性。每个BC都应该连接到一些数据访问或基础设施层,提供必要的对象,并注意从哪里获取这些对象等细节。

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

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

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

正是在反复思考这个问题时,我突然想起了我在吉米·博加德的一篇博客文章中读到的一条评论,他在文章中谈到了他所研究的一个领域模型,这个模型花了数周的时间才得出,因为他们对这个问题研究得越多,就越了解这个领域,并能够得出一个优雅的设计。

我清醒的时刻是,我不再考虑坚持,不再考虑这些BC中的客户。

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

现在,运营BC负责在系统中提出订单,他们需要一个客户的概念。然而,他们不需要知道MD是谁,也不需要知道联系人列表,谁关心客户的办公室?运营人员只需要知道客户名称,在我们的案例中,需要一个4个字符的客户代码。当我想清楚的时候,我甚至不需要将这些信息作为一个实体来保存,它可以只是我的Ops BC中的一个Value Object。但我如何将这些信息输入我的行动BC?这真的很简单。BC定义了一个接口,我的应用程序可以通过该接口与Ops BC进行交互。我的应用程序不知道Ops BC内部发生了什么,但它知道有一个Order Aggregator根,它有一个RaiseNewOrder的方法,该方法接受CustomerValueObject类型的参数。CustomerValueObject由一个4个字母的代码和一个Customer Name组成。因此,在我的应用程序UI中,我可以使用Sales(我们称之为Customer Management)BC来获取客户列表,以显示下拉列表。然后,当我处理操作的事务部分,即发布新订单信息的用户时,我使用Ops BC并传入customerVO作为参数。

因此,我的Ops BC和Sales BC是独立的、独立的,它们在内部有确保域完整性所需的概念,我能够在UI中访问我需要的数据,这样运营商就可以选择一个客户,然后在Ops BC中执行操作。

我意识到,客户对销售团队的作用与客户对运营团队的作用不同。对于运营人员来说,客户只是他们标记订单的东西。他们对该公司的内部运作或任何相关信息都不感兴趣。只要我有一个BCs客户的通用ID,我就有办法把我需要的数据提取出来。例如,我可以使用Ops BC来收回客户XXXX的订单列表。我不需要通过Customer Aggregate来实现这一点。

现在,把这个皱纹加进去。在某个时候,销售人员需要了解客户下的订单,所以我需要开始在我的销售BC中复制信息吗?这就是我陷入困境的地方,但后来我意识到我说的是销售人员,他们是UI的用户,在UI中我可以为给定的客户收回订单列表。

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

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

当我问了一个问题,比如"那么会计部门需要从这份订单中看到什么"时,这位脾气相当暴躁的运营总监对我说了一句话,他的回答是"我不在乎他们想要什么,我只想要我想要的"。我认为这就是你需要如何看待BCs。

不管怎样,我希望这个华夫饼能帮助到别人。

最新更新