如何处理轴突框架中从佐贺发送的命令

时间:2019-11-03 01:51:59

标签: cqrs saga axon

使用传奇,给定事件EventA,传奇开始,它发送一个(或多个)命令。 如何确保命令发送成功,然后其他微服务中的实际逻辑没有抛出,等等。

让我们举一个电子邮件传奇的例子: 当用户注册时,我们创建一个发布UserRegisteredEvent的用户聚合,将创建一个传奇,该传奇负责确保将注册电子邮件发送给用户(电子邮件中可能包含验证码,欢迎消息等)。

我们应该使用:

    尝试/捕获的
  1. commandGateway.sendAndWait->可扩展吗?

  2. commandGateway.send并使用最后期限,并使用某种“失败事件”,例如SendEmailFailedEvent->需要为命令关联“令牌”,以便可以将“ associationProperty”与正确的传奇关联 发送SendRegistrationEmailCommand

  3. commandGateway.send(...).handle(...)->句柄中是否可以引用MyEmailSaga中的eventGateway / commandGateway? 如果出错,我们发送事件?或者我们可以从我们的传奇实例中修改/调用方法。如果没有错误,则其他服务已发送了诸如“ RegistrationEmailSentEvent”之类的事件,因此传奇将结束。

  4. 使用截止日期,因为我们仅使用“发送”并且不处理可能无法发送的命令的最终错误(其他服务已关闭等)

  5. 还有其他东西吗?

  6. 还是所有这些的组合?

如何处理以下错误? (使用截止日期或.handle(...)或其他)

错误可能是:

  • 命令没有处理程序(没有服务启动等)

  • 命令已处理,但其他服务中引发了异常,并且未发送事件(其他服务中没有try / catch)

  • 命令已处理,引发并捕获了异常,其他服务发布了一个事件以通知其发送电子邮件失败(佐贺将接收事件并根据提供的事件类型和数据执行适当的操作->电子邮件错误或不存在,因此无需重试)

  • 我错过了其他错误吗?

@Saga
public class MyEmailSaga {

    @Autowired
    transient CommandGateway commandGateway;


    @Autowired
    transient EventGateway eventGateway;

    @Autowired
    transient SomeService someService;

    String id;
    SomeData state;
    /** count retry times we send email so can apply logic on it */
    int sendRetryCount;

    @StartSaga
    @SagaEventHandler(associationProperty = "id")
    public void on(UserRegisteredEvent event) {
        id = event.getApplicationId();
        //state = event........
        // what are the possibilities here? 
        // Can we use sendAndWait but it does not scale very well, right?
        commandGateway.send(new SendRegistrationEmailCommand(...));
        // Is deadline good since we do not handle the "send" of the command
    }

    // Use a @DeadlineHandler to retry ?

    @DeadlineHandler(deadlineName = "retry_send_registration_email")
    fun on() {
         // resend command and re-schedule a deadline, etc
    }

    @EndSaga
    @SagaEventHandler(associationProperty = "id")
    public void on(RegistrationEmailSentEvent event) {

    }

}

编辑(接受答案后):

主要有两个选项(对不起,但以下是kotlin代码):

第一个选项

commandGateway.send(SendRegistrationEmailCommand(...))
    .handle({ t, result ->
    if (t != null) {
       // send event (could be caught be the same saga eventually) or send command or both
    }else{
       // send event (could be caught be the same saga eventually) or send command or both
    }
    })
// If not use handle(...) then you can use thenApply as well
    .thenApply { eventGateway.publish(SomeSuccessfulEvent(...)) }
    .thenApply { commandGateway.send(SomeSuccessfulSendOnSuccessCommand) }

第二个选项: 使用截止日期来确保如果SendRegistrationEmailCommand失败并且您没有收到任何有关失败的事件(当您不处理发送的命令时),则可以执行传奇操作。

当然可以将截止日期用于其他目的。

成功接收到SendRegistrationEmailCommand时,接收方将发布一个事件,以便通知传奇故事并对其进行操作。 可以是RegistrationEmailSentEvent或RegistrationEmailSendFailedEvent。

摘要:

似乎最好仅在命令发送失败或接收方引发了意外异常时才使用handle(),如果这样,则发布事件以供传奇使用。 如果成功,接收者应发布事件,佐贺将监听该事件(并最终注册截止日期,以防万一)。接收方也可以发送事件以通知错误并且不抛出错误,佐贺也将侦听此事件。

1 个答案:

答案 0 :(得分:2)

理想情况下,您将使用异步选项来处理错误。这将是commandGateway.send(command)commandGateway.send(command).thenApply()。如果故障与业务逻辑相关,则在这些故障上发出事件可能是有意义的。一个简单的gateway.send(command)就有意义了; Saga可以对由此返回的事件做出反应。否则,您将不得不处理命令的结果。

您需要使用sendAndWait还是仅使用send().then...取决于失败时需要执行的活动。不幸的是,当异步处理结果时,您不能再安全地修改Saga的状态。轴突可能已经保留了传奇的状态,导致这些更改丢失了。 sendAndWait解决了这一问题。可伸缩性通常不是问题,因为可以根据您的处理器配置并行执行不同的Sagas。

Axon团队目前正在研究可能的API,这些API允许在Sagas中安全地异步执行逻辑,同时仍在保证线程安全性和状态持久性。