2
votes

Scenario: I have build a saga with 10 steps. It's updating various systems, and the entire saga could take a few minutes to complete.

The saga is started with data from another system where users type in informations on a customer.

I am not able to see when the user is done typing in data in their system, but i'm reading the data with changes from the system every x minutes.

My issue is, that whenever I start a saga with data on a customer I need to make sure that the previous saga on the same customer has finished. If the user is spending 10 minutes on typing in data, the system might start 5 flows on the same customer, and flows might overtake previous flows, and mess up data.

Does anyone have an idea on how I can resolve this?

Thanks in advance.

Ole

2
Could you make your unique attribute the customer id? If that's the case you can let the message that starts the saga also be mapped to and handled by the saga. That way, if saga with customer id 1 is started and a new message arrives (update received for customer 1), results in being handled by the same saga. If this doesn't work, could you please provide short code snippets about your saga mappings + the initialization messagejanpieter_z

2 Answers

0
votes

You cannot resolve this without changing the saga and probably the messages which are being sent by the client system.

Your problem is that the saga is probably configured to be started on receipt of a certain message type, which is generated by the client application every time the user makes a change to a customer.

Lets call this message:

public class ClientTypedSomethingAboutCustomer 
{
    int CustomerId {get;set;}
    ...
}

Your saga will be set up something like this:

public class CustomerSaga : Saga<CustomerSagaData>, IAmStartedByMessages<ClientTypedSomethingAboutCustomer>
{
    public override void ConfigureHowToFindSaga()
    {
        ConfigureMapping<ClientTypedSomethingAboutCustomer>
            (message => message.CustomerId).ToSaga(saga => saga.CustomerId);
        ...
    }
    ...
}

This causes the saga to be initialised and the Customer ID value set in the IContainSagaData implementation, for every single client message received.

To resolve the problem of further messages initializing new sagas, you could create another message type, to differentiate when someone starts to type something about a customer, and then types something else about that customer.

Something like:

public class ClientTypedSomethingElseAboutCustomer 
{
    int CustomerId {get;set;}
    ...
}

Then your saga would look like this:

public class CustomerSaga : Saga<CustomerSagaData>, IAmStartedByMessages<ClientTypedSomethingAboutCustomer>
    ,IHandleMessages<ClientTypedSomethingElseAboutCustomer>
{
    public override void ConfigureHowToFindSaga()
    {
        ConfigureMapping<ClientTypedSomethingAboutCustomer>
            (message => message.CustomerId).ToSaga(saga => saga.CustomerId);

        ConfigureMapping<ClientTypedSomethingElseAboutCustomer>
            (message => message.CustomerId).ToSaga(saga => saga.CustomerId);
    }
    ...
}

This will ensure that all messages about a customer will be routed to the single saga instance.

It may be possible to acheive an aproximate queuing behavior by running NServiceBus in single threaded mode. This may restrict the creation of concurrent sagas for a customer, but I wouldn't like to rely on this.

0
votes

Both @janpieter_z and @tom-redfern are correct: make sure you can map the saga to a customer id of some sort.

You could also implement complex saga finding logic to find the correct saga, corresponding to the message coming in.

Also, multiple messages can be set as "IAmStartedByMessages" if they don't arrive in the proper order.

Lastly, who is sending the messages to your saga? Shouldn't the saga be responsible for sending out requests to gather data? Perhaps every 10 seconds via timeouts or something, until nothing has been received for some minutes, or a handler replies with a different message?