Search code examples
nservicebusnservicebus-sagas

NServiceBus - queue up sagas with the same key


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


Solution

  • 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.