【问题标题】:Validation within a asynchronous SAGA pattern - CQRS & DDD异步 SAGA 模式中的验证 - CQRS 和 DDD
【发布时间】:2019-10-02 05:17:05
【问题描述】:

让我们考虑下面的流程:

  • API 客户端调用 [POST] /api/v1/invitation/:InvitationId/confirm
  • 在 SAGA 中确认邀请
  • 最终引发 InvitationConfirmed 事件以指示成功

我们很难找到一个好地方来验证我们传递给 SAGA 的“事件”。例如,我们要确保: - 指定的InvitationId 存在 - 相应的邀请未过期或已处理

我们尝试了几件事:

  1. 触发命令:

    • 触发命令RequestInvitationConfirmation
    • 同步处理此命令,如果命令无效则返回错误,否则引发InvitationConfirmationRequested 事件。

流程的其余部分相同

缺点: - 要求我们遵循“请求/响应”模式(在 HTTP 请求生命周期内同步)

  1. 引发事件:

    • 引发事件InvitationConfirmationRequested
    • 在 SAGA 中,查询Invitation 服务并执行验证。如果命令无效,我们发布事件InvitationConfirmationFailed (...)

缺点: - 据我了解,应该使用 SAGA 来编排流程。这里我们介绍“验证”的概念。我不确定这是推荐的方法。

验证是一个非常常见的概念。在分布式全异步系统中如何处理?

【问题讨论】:

    标签: validation domain-driven-design cqrs saga


    【解决方案1】:

    这个系统设计的重点是:“这个API的客户是谁?”

    • 如果此客户端是内部 ServiceApplication,那是一回事(例如在分布式应用程序、微服务等中)。
    • 如果 API 被第三方客户端使用,那就另当别论了。

    简答

    如果API在Services之间内部使用,在系统中发送带有无效Id的命令是错误的,因此应该由系统开发人员记录和检查。像这样的情况也应该通过手动修复它们(通过一些管理后端)来解决。记录这些内容并通知开发人员。

    如果 API 是从第三方应用程序中使用的,那么如何在 API 和它使用的系统的其他部分之间分离职责就很重要。让 API 负责验证,不要发送带有无效 ID 的命令。与第一种情况一样,将具有无效 ID 的命令视为错误。在这种情况下,如果您使用异步流,您将需要一种与第三方应用程序通信的方式来通知它。你可以使用WebHooks之类的东西。

    对于验证的第二部分,请检查 these series of blog postsoriginal paper

    长答案

    如果您四处搜索,您会看到很多关于错误和验证的讨论,所以这是我的看法。

    由于我们对系统的其他部分进行了分离,因此分离我们所拥有的错误类型似乎很自然。您可以在该主题上查看this paper

    让我们定义一些错误类型。

    • 域错误
    • 应用程序错误
    • 技术错误(数据库连接丢失等)

    因为我们有不同类型的错误,所以验证应该从我们系统的不同部分执行。

    这些错误的通信也可以通过不同的机制来完成,具体取决于:

    • 操作的请求者和接收者
    • 使用的通信渠道
    • 通信类型:同步或异步

    现在您拥有的验证是:

    • 验证具有指定IdInvitation 存在
    • 验证Invitation 没有过期
    • 验证Invitation 尚未处理(接受、拒绝等)

    如何处理这将取决于我们如何分离应用程序中的职责。让我们使用DesignByContract 原则并定义明确的规则,每一层(域、应用程序等)应该从其他层得到什么。

    让我们定义一个规则,即不应创建和调度包含与现有 Invitation 不对应的 InvitationId 的命令。

    请注意,此处使用的术语可能会有很大差异,具体取决于项目中使用的架构类型(分层架构、六边形等)

    这会强制CommandCreator 在调度命令之前验证Invitation 是否存在指定的Id

    在使用 API 的情况下,将接受请求的 RouteHandler(应用控制器等)必须:

    • 自己执行此验证
    • 委托他人进行验证

    让我们进一步定义这是我们ApplicationLayer 的一部分(或模块、组件等,不管它如何调用,所以我将使用Layer)并将其设为ApplicationError。从这里我们可以通过许多不同的方式来做到这一点。

    一种方法是使用DispatchConfirmInvitationCommandApplicationService,它会询问DomainLayer 是否存在带有请求的IdInvitation,如果不存在则引发错误(例如抛出异常)。此错误将由RouteHandler 处理并发送回请求者。

    您可以同时使用同步和异步通信。如果它是异步的,您将需要为此创建一个机制。您可以参考EnterpriseIntegrationPatterns了解更多信息。

    这里的要点是:它不是域的一部分

    从这里开始,我们系统中的其他所有人都应该认为ConfirmInvitationCommand 中带有指定Id 的邀请存在。如果没有,则将其视为系统故障,应由开发人员和/或管理员进行检查。应该有手动方式(后台管理)来取消这些无效命令,所以在开发系统时必须考虑到这一点,并视为系统中的故障。

    其他两个验证是Domain 的一部分。

    假设你有一个

    • Invitation聚合
    • InvitationConfirmationSaga

    让我们让这些聚合与消息进行通信。让我们定义这些类型的消息:

    • RequestConfirmInvitation
    • InvitationExpired
    • InvitationAlreadyProcessed

    这是基本流程:

    • ConfirmInvitationCommand 开始一个InvitationConfirmationSaga

    • InvitationConfirmationSaga 发送RequestConfirmInvitation 消息到Invitation

    然后:

    • 如果Invitation 过期,它会发送InvitationExpired 消息到InvitationConfirmationSaga

    • 如果Invitation 被处理,它会将InvitationAlreadyProcessed 消息发送到InvitationConfirmationSaga

    • 如果Invitation 没有过期,它会被接受并将InvitationAccepted 消息发送到InvitationConfirmationSaga

    然后:

    • InvitationConfirmationSaga 将收到这些消息并相应地引发事件。

    这样您就可以将域逻辑保存在 Domain 中,在本例中为 Invitation 聚合。

    【讨论】:

      【解决方案2】:

      您有一个包含InvitationId 的命令ConfirmInvitation。您将其从InvaitationAppService 发送到您的Invitation 域。您的Invitation 域应如下所示

      ...
      public void ConfirmInvitation()
      {
          if (this.Status == InvitationStatus.Confirmed)
              throw new InvalidInvitationException("Requested invitation has already been confirmed");
          //check more business logic here
          this.Status = InvitationStatus.Confirmed;
          Publish(new InviationConfirmedEvent(...));
      }
      ...
      

      您的InvitationAppService 应该有如下内容:

      ...
      public void ConfirmInvitation(Guid invitationId)
      {
          // rehydrate your domain from eventstore
          var invitation = repo.GetById<Invitation>(invitationId);
          if (invitation == null)
              throw new InvalidInvitationException("Invalid Invitation requested");
          invitation.ConfirmInvitation(new ConfirmInvitation(...));
      }
      

      您无需介绍新事件InvitationConfirmationRequested。 DDD 是一种方法,您的域/业务验证应驻留在域内。不要试图在您的领域中适应其他模式或技术。在 saga 中验证您的域(用于协调跨服务分发事务)可能会造成复杂性和混乱

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-21
        • 1970-01-01
        • 2015-09-14
        • 2019-03-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多