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

时间:2012-11-23 13:57:34

标签: c# design-patterns domain-driven-design cqrs bounded-contexts

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

  1. 员工管理
  2. 采购
  3. 存档
  4. 报告
  5. 我希望它尽可能地插件,所以我可以单独开发和维护它们。可能他们会公开WCF或Web API与他们进行交互。

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

    所以问题:

      

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

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

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

    在存档中,某些信息将被存档并与部门绑定,依此类推。

    这同样适用于员工。

      

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

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

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

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

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

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

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

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

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

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

2 个答案:

答案 0 :(得分:10)

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

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

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

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

作为旁注,我不了解您的域名,但您可能需要重新考虑您的上下文边界,例如将分娩与购买分开。

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

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

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

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

答案 1 :(得分:4)

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

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

当我一遍又一遍地思考这个问题时,我想起了我在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个字母的代码和客户名称组成。因此,在我的App UI中,我可以使用销售(让他们称之为客户管理)BC来获取客户列表以显示下拉列表。然后,当我处理操作的事务部分,即用户发布新订单信息时,我使用Ops BC并传递customerVO作为参数。

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

我意识到销售团队的客户与Ops团队的客户不同。对于Ops人来说,客户只是他们标记订单的东西。他们对该公司的内部运作或任何有关它的信息不感兴趣。只要我在整个BC中为客户提供了一个通用ID,我就有办法将这些数据拉回到我需要进行演示的ID中。例如,我可以使用Ops BC来撤回客户XXXX的订单列表。我不需要通过客户汇总来进行此操作。

现在,抛出这个皱纹。销售人员在某些时候需要知道客户的订单,所以我需要在我的销售BC中开始复制信息吗?这就是我陷入困境的地方,但后来我意识到我在谈论销售人员,他们是用户界面的用户,在UI中我可以撤回给定客户的订单列表。

我仍然是DDD的新手,但我意识到DDD是关于与域专家的对话,当你正确地进行这些对话时,你会意识到你认为存在于BC的实体......并没有# 39;吨

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

对我来说,这笔交易的评论是来自相当脾气暴躁的Ops Director,当时我问了一个类似的问题,那么会计部门需要从这个订单中看到什么呢?"而且他的反应,减去咒骂是"我不在乎他们想要什么,我只想要我想要的东西"。而且我认为你需要如何看待BC。

我希望这个华夫饼无论如何能帮助别人。