假设我有经典的客户有限上下文和 MakeCustomerPreferred
命令,并且我有一些横切问题,例如日志记录和授权。尽管处理这些横切问题的方法已经被广泛讨论,但我很好奇我们希望记录有关用户或负责在域事件本身中发出命令的服务的信息。
示例事件:
interface CustomerMadePreferred {
customerId: string,
issuingService: string,
issuingUser: string,
}
示例命令:
interface MakeCustomerPreferred {
customerId: string
}
使用上面的命令,我们将缺乏详细说明发出用户和服务的上下文。我们可以要求客户端提供 issuingService
和 issuingUser
的值作为命令的一部分,但问题是命令通常由客户端提交,让我们假设这种情况客户端是应用程序控制之外的网络浏览器,我们无法控制用户可以在命令中提交哪些值。此外,如果我们的服务位于 ReSTful 或 JSON Web API 后面,那么我们通常可以通过使用 OAuth token 之类的东西来确定这些值。这给我留下了一些明显的策略。
可能的策略:
- 在命令中包含
issuingService
和issuingUser
,然后根据从身份验证上下文确定的值验证它们。这允许客户端发出的命令与处理程序处理的命令相同,并保持处理程序实现简单,因为它只需要一个命令参数,不需要传入身份验证上下文。 - 从身份验证上下文中提取
issuingService
和issuingUser
并将这些详细信息添加/注入(inject)到移交给命令处理程序的命令中。这使得客户端更容易发出原始命令,但命令处理程序和客户端之间的命令约定略有不同。示例:Object.assign({}, makeCustomerPreferredCommand, authContext);
- 将身份验证上下文作为单独的参数传递给命令处理程序,或使其可从可由命令处理程序调用的服务中使用。这保留了命令的约定,但使命令处理程序的实现变得复杂。
我倾向于选项1和2,主要是因为它似乎允许使用功能性方法来实现命令处理程序。也许我遗漏了一些选项或忽略了一些细节,这些细节可能会导致不必要的事件中包括 issuingService
和 issuingUser
。
更新: 另一种方法,我们称之为选项 4,可能是不在生成的 CustomerMadePreferred 事件中包含这些详细信息,而是处理并发出单独的 CommandIssued 事件,这些事件可能包含某种类型的键,可用于将颁发者信息与命令处理程序发出的事件相关联。
最佳答案
我几乎喜欢选项 4——我的感觉是相关 ID 是错误的机制。相反,我会使用因果关系 ID;相关 ID 的含义略有不同。 command.id:N 生成的所有事件都会有 event.causationId:N,您可以返回到命令历史记录(域外)以检查域外的任何问题。
也就是说,您可能需要了解谁想要捕获此信息。如果是您的 devOps 团队,那么保持域事件干净可能是正确的选择,只需链接到如上所述的命令历史记录即可。
但是,如果您的领域专家想要这个,那应该会引起人们的注意,因为这表明这里隐藏着一些领域概念,需要从普遍存在的语言中剔除出来并将其合并到您的模型中。
(与 Don't Create Aggregate Roots 相比;对客户帐户的更改并非凭空而来。它们真的来自域内吗?如果是这样,那么可能需要跟踪。)
关于typescript - 在CQRS/ES系统中记录发行服务和用户的事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36165939/