Question

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top