Question

I have two messages , clientChangeMessage( responsible for creating the client) and clientContractChangeMEssage( responsible for the booking details of the client). Now in my database a client cannot be created until it has the client contract and vice-versa. On my local system everything is working fine i.e. if get a client change message first i store it in the saga and wait for the client contract message and when that arrives the saga executes both the messages. But on my testers machine when the client change message comes it gets stored in the saga but when a client contract change comes the saga does not find the client change saga and hence creates another saga. I have tried it with the exact same messages that my tester has tried ,it works on my machine, and am unable to figure out what might be going wrong. I am using raven db persistence. (Sorry i could not think of pasting any code for this)

ClientSagaState

public class ClientSagaState:IContainSagaData
    {
        #region NserviceBus
        public Guid Id { get; set; }
        public string Originator { get; set; }
        public string OriginalMessageId { get; set; }
        #endregion

        public Guid ClientRef { get; set; }

        public ClientMessage ClientChangeMessage { get; set; }

        public ClientContractChangeMessage ClientContractChange { get; set; }


    }


    public  class ClientSaga:Saga<ClientSagaState>,
           IAmStartedByMessages<ClientChangeMessage>,
           IAmStartedByMessages<ClientContractChangeMessage>
       {

        public override void ConfigureHowToFindSaga()
           {

               ConfigureMapping<ClientChangeMessage>(s => s.ClientRef, m => m.EntityRef);
               ConfigureMapping<ClientContractChangeMessage>(s => s.ClientRef, m => m.PrimaryEntityRef);
           }

        public void Handle(ClientChangeMessage message)
           {

               if (BusRefTranslator.GetLocalRef(EntityTranslationNames.ClientChange, message.EntityRef.Value) != null)
               {

                   GetHandler<ClientChangeMessage>().Handle(message);
                   CompleteTheSaga();
                   return;
               }
               HandleServiceUserChangeAndDependencies(message);
               //MarkAsComplete();
               CompleteTheSaga();
           }

          public void Handle(ClientContractChangeMessage message)
            {
                var state=this.Data;
                //Some handling logic
                //Check if client is not in database then store the state
                state.ClientContractChange=message;
                state.ClientRef =message.PrimaryEntityRef;
                //if client is in the data base then 
                MarkAsComplete();


       }

Thanks,

Was it helpful?

Solution

Because you are mapping to the saga data via the ClientRef property, you need to tell the persistence (Raven in this case) that this property is unique. What is probably happening is that, in some cases (it comes down to a race condition) the query done on the Raven index by the second message retrieves stale data, assumes there is no saga data, and creates new.

This should fix your issue:

[Unique]
public Guid ClientRef { get; set; }

With this information, the Raven saga persister will create an additional document based on this property (because loading by Id in Raven is fully atomic) so that the second message will be sure to find it.

If you were using another persistence medium like NHibernate, the same attribute would be used to construct a unique index on that column.

Edit based on comment

The unique constraint document and your saga data will be fully consistent, so depending on timing of incoming messages, one of 3 things will happen.

  1. The message is truly the first message to arrive and be processed, so no saga data is found, so it is created.
  2. The message is the second to arrive, so it looks for the saga data, finds it, and processes successfully.
  3. The 2nd message arrives very close to the first message, so they are both processing in separate threads at the same time. Both threads look in the saga data and find nothing, so they both begin to process. The one that finishes first commits successfully and saves its saga data. The one that finishes second attempts to save the saga data, but finds that while it's been working the other thread has moved its cheese, so Raven throws a concurrency exception. Your message goes back on the queue and is retried, and now that the saga data exists, the retry acts like Scenario #2.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top