这个系统设计的重点是:“这个API的客户是谁?”。
- 如果此客户端是内部
Service 或 Application,那是一回事(例如在分布式应用程序、微服务等中)。
- 如果 API 被第三方客户端使用,那就另当别论了。
简答
如果API在Services之间内部使用,在系统中发送带有无效Id的命令是错误的,因此应该由系统开发人员记录和检查。像这样的情况也应该通过手动修复它们(通过一些管理后端)来解决。记录这些内容并通知开发人员。
如果 API 是从第三方应用程序中使用的,那么如何在 API 和它使用的系统的其他部分之间分离职责就很重要。让 API 负责验证,不要发送带有无效 ID 的命令。与第一种情况一样,将具有无效 ID 的命令视为错误。在这种情况下,如果您使用异步流,您将需要一种与第三方应用程序通信的方式来通知它。你可以使用WebHooks之类的东西。
对于验证的第二部分,请检查 these series of blog posts 和 original paper。
长答案
如果您四处搜索,您会看到很多关于错误和验证的讨论,所以这是我的看法。
由于我们对系统的其他部分进行了分离,因此分离我们所拥有的错误类型似乎很自然。您可以在该主题上查看this paper。
让我们定义一些错误类型。
- 域错误
- 应用程序错误
- 技术错误(数据库连接丢失等)
因为我们有不同类型的错误,所以验证应该从我们系统的不同部分执行。
这些错误的通信也可以通过不同的机制来完成,具体取决于:
- 操作的请求者和接收者
- 使用的通信渠道
- 通信类型:同步或异步
现在您拥有的验证是:
- 验证具有指定
Id 的Invitation 存在
- 验证
Invitation 没有过期
- 验证
Invitation 尚未处理(接受、拒绝等)
如何处理这将取决于我们如何分离应用程序中的职责。让我们使用DesignByContract 原则并定义明确的规则,每一层(域、应用程序等)应该从其他层得到什么。
让我们定义一个规则,即不应创建和调度包含与现有 Invitation 不对应的 InvitationId 的命令。
请注意,此处使用的术语可能会有很大差异,具体取决于项目中使用的架构类型(分层架构、六边形等)
这会强制CommandCreator 在调度命令之前验证Invitation 是否存在指定的Id。
在使用 API 的情况下,将接受请求的 RouteHandler(应用控制器等)必须:
让我们进一步定义这是我们ApplicationLayer 的一部分(或模块、组件等,不管它如何调用,所以我将使用Layer)并将其设为ApplicationError。从这里我们可以通过许多不同的方式来做到这一点。
一种方法是使用DispatchConfirmInvitationCommandApplicationService,它会询问DomainLayer 是否存在带有请求的Id 的Invitation,如果不存在则引发错误(例如抛出异常)。此错误将由RouteHandler 处理并发送回请求者。
您可以同时使用同步和异步通信。如果它是异步的,您将需要为此创建一个机制。您可以参考EnterpriseIntegrationPatterns了解更多信息。
这里的要点是:它不是域的一部分
从这里开始,我们系统中的其他所有人都应该认为ConfirmInvitationCommand 中带有指定Id 的邀请存在。如果没有,则将其视为系统故障,应由开发人员和/或管理员进行检查。应该有手动方式(后台管理)来取消这些无效命令,所以在开发系统时必须考虑到这一点,并视为系统中的故障。
其他两个验证是Domain 的一部分。
假设你有一个
-
Invitation聚合
-
InvitationConfirmationSaga
让我们让这些聚合与消息进行通信。让我们定义这些类型的消息:
RequestConfirmInvitation
InvitationExpired
InvitationAlreadyProcessed
这是基本流程:
然后:
如果Invitation 过期,它会发送InvitationExpired 消息到InvitationConfirmationSaga
如果Invitation 被处理,它会将InvitationAlreadyProcessed 消息发送到InvitationConfirmationSaga
如果Invitation 没有过期,它会被接受并将InvitationAccepted 消息发送到InvitationConfirmationSaga
然后:
-
InvitationConfirmationSaga 将收到这些消息并相应地引发事件。
这样您就可以将域逻辑保存在 Domain 中,在本例中为 Invitation 聚合。