【问题标题】:Is my Saga structure the correct solution? (NServiceBus)我的 Saga 结构是正确的解决方案吗? (NServiceBus)
【发布时间】:2011-10-26 04:17:42
【问题描述】:

我会直接切入。我正在尝试通过发送给我们银行的文件自动执行信用卡付款。银行卡付款未实时验证。银行通宵处理付款,并在第二天发送响应文件,其中包含成功和不成功的付款。

我有一个网络应用程序,当它接受或取消付款时,会(通过 Bus.Send)将包含付款/取消详细信息的消息发送到命令消息处理器。

然后处理器发布(通过 Bus.Publish)这个供所有服务查看。

一个服务需要做以下事情:

  • 收到第一条消息后开始一个传奇
  • 发出暂停营业的超时请求
  • 跟踪所有进入的后续消息(在 saga 中)
  • 收到超时后,将付款和取消消息转入银行文件。

问题是我不知道如何在 saga 中存储消息集合(或其他任何内容),因为 List 不允许作为虚拟成员。

这是当前的 saga 结构:

public class PaymentRequestCancelledSagaBase : IContainSagaData
    {
        // the following properties are mandatory
        public virtual Guid Id { get; set; }
        public virtual string Originator { get; set; }
        public virtual string OriginalMessageId { get; set; }

        // List of all the received PaymentRequestedMessages
        public virtual List<PaymentRequested> PaymentRequestedMessages;

        // List of all the received PaymentCancelledMessages
        public virtual List<PaymentCancelled> PaymentCancelledMessages;
    }

有什么想法吗?

【问题讨论】:

  • IList 受支持,请参阅制造示例。我会将付款 ID 列表保留在该列表中
  • 所以银行文件处理器会使用这些支付 ID 来查找交易金额,而不是在消息中传输这些金额?在这种情况下,如果消息中没有数据,我需要向处理器添加数据访问层以获取数据。
  • 您也可以存储该信息,就像在制造示例中使用 OrderLine (DTO)。

标签: c# nhibernate nservicebus saga


【解决方案1】:

我不确定我的想法是否与 Udi 等人一致,但我一直认为 saga 数据是关于消息的非常轻量级的 元数据,它不应该包含太多实际的消息数据。

可以为每个付款请求和相应的批准/取消设置一个传奇,但假设银行要求您在每个工作日将它们全部一起批量处理,或者向您收取每个文件的固定金额,这很常见……

在这种情况下,您可能会在消息传递系统 (NServiceBus) 的基础上或应该拥有某种事务系统,该系统实际上跟踪事务本身。如果它们被分组到某种类型的批次中(即一个工作日),那么该批次必须有一个 ID。 是 saga 应该引用的内容,以及有关整个过程状态的基本信息(即您是否收到银行的回复?)。

服务总线本身并不是一个系统,它是独立系统和组件相互通信的一种方式。 Sagas 实际上打算在它们完成后被删除(通过MarkAsComplete),所以它们真的不是存储“永久”信息的地方。

在我的世界里,saga 数据看起来应该是这样的:

public class PaymentProcessingSagaData : IContainSagaData
{
    public virtual Guid Id { get; set; }
    public virtual string Originator { get; set; }
    public virtual string OriginalMessageId { get; set; }

    public virtual int RequestBatchId { get; set; }
    public virtual DateTime? WhenRequestBatchClosed { get; set; }
    public virtual string BankRequestFileName { get; set; }
    public virtual DateTime? WhenRequestFileSent { get; set; }
    public virtual string BankResponseFileName { get; set; }
    public virtual DateTime? WhenResponseFileReceived { get; set; }
    public virtual int PaymentBatchId { get; set; }
}

对应的操作顺序如下:

  1. 请求发送到应用程序,该应用程序创建/附加到批处理并发布PaymentRequested 事件。
  2. 订阅者接收PaymentRequested 事件,如果需要,创建一个新的saga 并设置RequestBatchId,它成为saga 相关ID。然后它设置关闭业务的超时时间。
  3. 超时处理程序关闭批处理,设置WhenRequestBatchClosed,并发布PaymentRequestBatchClosed 事件。
  4. 订阅者收到PaymentRequestBatchClosed 事件,创建支付文件(设置BankRequestFileName),发布RequestFileAvailable 事件,表明文件已准备就绪。
  5. 订阅者接收RequestFileAvailable 事件并启动上传过程,该过程(例如)将文件通过FTP 发送到银行服务器,更新WhenRequestFileSent,并发布RequestFileSent 事件。
  6. 监控进程(或其他超时处理程序)检测响应文件,更新BankResponseFileNameWhenResponseFileReceived 并发布ResponseFileAvailable 事件。
  7. 订阅者接收ResponseFileAvailable 事件、处理文件、创建付款批次、更新PaymentBatchId,并可选择发布最终PaymentsProcessed 事件并完成传奇。

当然,您不一定需要这些When DateTime 字段,您可以轻松地使用布尔标志来指示哪些步骤已完成。但重要的是,您的 saga 跟踪的是交易 state,而不是交易 data

不要犯试图将所有内容都塞进传奇的错误。它并不是要取代事务系统,它只是将它们粘在一起的一点粘合剂。

【讨论】:

  • Web 应用程序不知道可能添加或未添加多少付款,也不会生成批次。在收到超时消息并整理消息之前,不会发生批次生成。处理器生成的唯一标识符实际上是批次 ID。这包含在银行文件和发送文件后发送的 BankFileSent 消息中。另一个处理器使用 BankFileSent 并更新付款记录。当响应返回时,我们将获得唯一标识符和最终更新的付款 ID。
  • @Fellmeister:恐怕你走错路了。 saga 不是事务系统或数据库的替代品,它是一个扩展。欢迎您尝试在没有一个的情况下继续,但我非常强烈的建议 - 我认为许多其他人也会推荐 - 是您在永久的特定于应用程序的 OLTP 数据库中跟踪交易数据(支付请求),而不是尝试在 saga 的范围内执行此操作。让 SQLExpress 或 Firebird 数据库与 NH 或 EF 一起使用不会超过几个小时,并且会消除很多未来的麻烦。
  • 因此,在 saga 通过其 ID 处理批处理的情况下,当文件准备好编译时,FileCreator 应用程序是否应该查询实时数据库以获取要放入文件的事务详细信息基于 PaymentID?
  • 我已经检查了我的设计并对其进行了修改,并考虑了您的传奇设计及其所服务的目的。感谢您花时间向我解释这些要点,这是一条漫长的道路,我还有很长的路要走。
  • @Fellmeister:这就是我会做的,是的。很高兴听到您正在取得进展。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-17
  • 1970-01-01
  • 1970-01-01
  • 2021-07-29
相关资源
最近更新 更多