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:
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?
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:
PaymentRequested
event.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.WhenRequestBatchClosed
, and publishes a PaymentRequestBatchClosed
event.PaymentRequestBatchClosed
event, creates the payment file (sets BankRequestFileName
), publishes a RequestFileAvailable
event saying that the file is ready.RequestFileAvailable
event and initiates upload process which (for example) FTPs the file to the bank server, updates WhenRequestFileSent
, and publishes a RequestFileSent
event.BankResponseFileName
and WhenResponseFileReceived
and publishes a ResponseFileAvailable
event.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.