【问题标题】:Handling duplication of domain logic using DDD and CQRS使用 DDD 和 CQRS 处理域逻辑的重复
【发布时间】:2014-09-20 13:33:28
【问题描述】:

我正在试验 DDD + CQRS,我不明白如何处理这个域逻辑重复问题:

首先,关于跨域重复:

场景 1: 假设我有一些处理办公室员工的应用程序。我有 3 个有界上下文:程序员部门、QA 部门和审计部门。每个 BC 都有自己的 AR:“程序员”、“测试人员”、“工人”。它们有 99% 的不同,每个都有不同的逻辑,但是,每个都有“姓名”、“姓氏”和一个简单的方法“getFullName”,它将这两者连接起来。

问题 1:我如何(并且应该?)使通用方法在每个 AR 中不重复?

最简单的答案可能是创建一些共享的“人类”类,并让这 3 个 AR 派生自它,但这与 DDD 的想法背道而驰,因为“QA 部门”永远不需要“getFullName”方法,而是需要一些其他“共享”方法。因此,此解决方案将使用未使用的方法向域发送垃圾邮件。

现在关于 CQRS 代码重复:

场景 2: 数据库包含发票。每张发票都有字段“sum”和“tax”。在“显示发票”页面中,我需要显示含税的发票金额。因此,在我的读取模型中,我需要执行“total = sum + tax”来将其展示给最终用户。但是,用户可以按“批准”按钮,比方说,它应该在其他数据库(会计或其他数据库)中注册发票金额。因此,在我的写入模型中,我将再次需要执行“total = sum + tax”。

问题 2:我如何(并且应该?)删除这种重复?

当然,这是一个简单的场景,但在分析了我的一些现实生活应用程序后,我发现使用 CQRS 需要在不同的地方进行大量重复,因为有很多地方是根据数据计算最终结果的存储在数据库中,并在查询和命令操作中完成。

有什么想法吗?我错过了什么吗?

【问题讨论】:

  • 场景 1 - 我只是做复制,如果相同的方法在 3 个有界上下文中,每个可能都有单独的更改理由。

标签: language-agnostic domain-driven-design cqrs


【解决方案1】:

场景 1

  1. 将代码复制并粘贴到三个有界上下文中的每一个中。
  2. 为包含在共享库中的名称创建一个值对象,该库封装了获取全名的逻辑。
  3. 创建负责管理员工详细信息的员工有界上下文。然后,任何其他有界上下文都可以使用它来查找员工的详细信息。将发布事件以确保有界上下文之间的一致性(例如,EmployeeJoinedCompanyEvent 包含其全名)。

场景 2

任何计算都应该是您的域模型的一部分。包含在实体、值对象或域服务中。

然后,任何计算的结果(在本例中为总计)都包含在从域发布的事件中。读取和写入数据存储都可以根据已发布事件中包含的值进行更新。他们不应该自己做任何计算。

例如,如果域发布InvoiceApprovedEvent,它将包含读取模型所需的所有数据。包括总税额和总金额。

事件也是有界上下文之间集成的主要方式。因此,如果您需要更新会计有界上下文或外部系统,您可以从 Invoicing 有界上下文订阅相关事件并在收到事件时进行处理。

参考文献

我强烈推荐几个资源来实现 DDD 和 CQRS(假设您已经熟悉 Eric Evan 的 DDD 书籍)。

【讨论】:

  • 谈论场景2:读取模型的整个想法不是完全不使用您的域层吗?从本质上讲,它应该尽可能靠近数据库,以跳过领域层的所有开销,并以您需要的方式查询您需要的数据。
  • 关于场景 1:似乎合乎逻辑,正如 Adrian 在他的评论中提到的那样,这些看起来相似但性质可能不同。
  • 场景 2 在我看来不像纯粹的读取模型。将总和写入会计数据库并不是一个无辜的行为——您选择将其标记为“批准”,这可能意味着它在您的通用语言中占有一席之地。会计软件可以根据您批准的价值做出决策,同时发票中会发生未经批准的更改。如果您想要一个单一的事实来源来跟踪所发生的事情,您最好有一个包含总和的InvoiceTotalApproved 域事件。
【解决方案2】:

场景 1

主数据管理之类的东西通常是其自身的有界上下文。这是员工姓名、出生日期等所属的地方。其他有界上下文以及各种读取模型可以从那里(直接或间接)检索它们的数据。

场景 2

如果您只是创建简单的总和,我不介意在上下文中有一些重复。一旦你有了更复杂的计算方法,它们应该清楚地与它们各自的有界上下文相关联。在您的示例中,可能存在 Invoicing 有界上下文,这是与发票相关的算法、计算和服务所属的自然位置。然后将从那里传播发票的创建和批准以填充受影响的读取模型。

【讨论】:

  • 看来您对方案 1 的看法是正确的,这似乎是最合乎逻辑的方法。至于方案 2,我仍然不太确定应该如何实施。假设我想要一个表格,其中列出了应用程序生命周期内的所有发票,按该总和排序并分页。控制器采用pageNum 参数和sortBy 参数并...将它传递到哪里?直接到Invoicing BC 的某个存储库?
  • 可能有一个报告 BC 汇总并提供这些信息。
  • 但这是否意味着我需要为每个需要在查询中进行某种计算的表单使用单独的 BC?例如,全局发票列表可能有 3 列,一些“上个月发票”有 6 列,每个客户的“发票历史记录”有 12 列。这是否需要 3 个单独的 BC,用不同的数据填充不同的发票?然后我们再次回到场景 1,因为我们有 3 个不同的 BC,它们使用相同的规则计算它们的一些列,而使用它们自己的规则计算它们自己的一些列。有什么想法吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-27
  • 2019-05-09
  • 2019-03-01
相关资源
最近更新 更多