我的 Saga 结构是正确的解决方案吗? (N服务总线)

发布于 2024-12-11 16:33:18 字数 1036 浏览 2 评论 0原文

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

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

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

一个服务需要执行以下操作:

  • 收到第一条消息后启动传奇
  • ,发出超时请求以结束业务,
  • 在收到
  • 超时后跟踪所有后续消息(在传奇中),将付款和取消消息转换为银行档案。

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

这是当前的传奇结构:

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;
    }

有什么想法吗?

I'll cut straight to it. I'm trying to automate credit card payments via a file sent to our bank. Card payments are not validated with the bank in real time. The bank processes payments overnight and sends out a response file the following day with both successful and unsuccessful payments.

I've a web app that when it accepts or cancels a payment, a message containing the details of the payment/cancel is sent (via Bus.Send) to a command message processor.

The processor then publishes (via Bus.Publish) this for all services to see.

One service needs to do the following:

  • start a saga with the receipt of the first message
  • issue a timeout request for the close of business
  • track all subsequent messages as they come in (in saga)
  • upon receipt of timeout, turn payment and cancellation messages into bank file.

The problem is I don't know how to store collections of messages (or anything else for that matter) in a saga as List<>'s aren't allowed as virtual members.

Here's the current saga structure:

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;
    }

Any thoughts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

爱冒险 2024-12-18 16:33:18

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

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

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

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

在我的世界中,传奇数据看起来像这样:

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 字段,您可以轻松地使用布尔标志来指示哪些步骤已完成。但重要的是你的传奇正在跟踪交易状态,而不是交易数据

不要犯这样的错误:把一切都塞进一个传奇里。它并不是要取代交易系统,它只是将它们粘合在一起的一点粘合剂。

I'm not sure if my thinking is in line with Udi et al, but I've always understood saga data to be very lightweight metadata about the messages, it shouldn't contain much actual message data.

You could have a saga for each payment request and corresponding approval/cancellation, but let's assume that the bank either requires you to batch them all together per business day or charges you a fixed amount per file, which is common...

In that case, underlying the messaging system (NServiceBus) you likely do or should have some sort of transactional system which is actually tracking the transactions themselves. If they're being grouped into some type of batch (i.e. a business day) then the batch must have an ID. That is what the saga should be referencing, in addition to basic information about the state of the entire process (i.e. did you get the response from the bank yet?).

A service bus isn't a system in and of itself, it's a way for independent systems and components to communicate with each other. Sagas are actually intended to be deleted once they're finished (via MarkAsComplete) so they're really not the place to store "permanent" information.

In my world the saga data would look something like this instead:

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; }
}

That corresponds to an order of operations like:

  1. Request sent to app, which creates/appends to batch and posts a PaymentRequested event.
  2. Subscriber picks up PaymentRequested event, and if necessary creates a new saga and sets RequestBatchId, which becomes the saga correlation ID. Then it sets the timeout for close of business.
  3. Timeout handler closes the batch, sets WhenRequestBatchClosed, and publishes a PaymentRequestBatchClosed event.
  4. Subscriber picks up PaymentRequestBatchClosed event, creates the payment file (sets BankRequestFileName), publishes a RequestFileAvailable event saying that the file is ready.
  5. Subscriber picks up RequestFileAvailable event and initiates upload process which (for example) FTPs the file to the bank server, updates WhenRequestFileSent, and publishes a RequestFileSent event.
  6. Monitoring process (or another timeout handler) detects response file, updates BankResponseFileName and WhenResponseFileReceived and publishes a ResponseFileAvailable event.
  7. Subscriber picks up ResponseFileAvailable event, processes the file, creates the payment batch, updates the PaymentBatchId, and optionally posts a final PaymentsProcessed event and completes the saga.

Of course you don't necessarily need these When DateTime fields, you could just as easily have boolean flags indicating which steps are complete. But the important thing is that your saga is keeping track of the transaction state, not the transaction data.

Don't make the mistake of trying to jam everything into a saga. It's not meant to take the place of a transactional system, it's just a bit of glue to hold it all together.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文