使用乌鸦数据库持久性的Saga错误nservicebus

时间:2013-10-23 10:52:06

标签: nservicebus saga

我有两条消息,clientChangeMes​​sage(负责创建客户端)和clientContractChangeMEssage(负责客户端的预订详细信息)。现在在我的数据库中,只有客户端合同才能创建客户端,反之亦然。在我的本地系统上一切正常,即如果首先获得客户端更改消息,我将其存储在传奇中并等待客户端合同消息,当它到达时,saga执行这两个消息。但是在我的测试人员机器上,当客户端更改消息时,它会被存储在传奇中,但是当客户端合同发生变化时,传奇不会发现客户端更改传奇并因此创建另一个传奇。我用我的测试人员试过的完全相同的消息尝试了它,它在我的机器上工作,我无法弄清楚可能出错的地方。我正在使用raven db persistence。 (抱歉,我想不出为此粘贴任何代码)

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();


       }

谢谢,

1 个答案:

答案 0 :(得分:1)

因为您通过ClientRef属性映射到saga数据,所以您需要告诉持久性(在本例中为Raven)此属性是唯一的。可能发生的事情是,在某些情况下(归结为竞争条件),第二条消息对Raven索引进行的查询检索过时数据,假设没有传奇数据,并创建新数据。

这可以解决您的问题:

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

有了这些信息,Raven saga persister会根据这个属性创建一个额外的文档(因为在Raven中按Id加载是完全原子的),这样第二条消息肯定会找到它。

如果您使用的是另一个像NHibernate这样的持久性媒体,则会使用相同的属性在该列上构建唯一索引。

根据评论进行修改

唯一约束文档和您的传奇数据将完全一致,因此根据传入消息的时间安排,将会发生以下三种情况之一。

  1. 该消息确实是第一个到达并被处理的消息,因此没有找到任何传奇数据,因此它被创建。
  2. 该消息是第二个到达的消息,因此它会查找传奇数据,查找并成功处理。
  3. 第二条消息非常接近第一条消息,因此它们同时在不同的线程中处理。两个线程都查看了saga数据,什么都没找到,所以它们都开始处理。完成的第一个成功提交并保存其传奇数据。完成第二次尝试保存saga数据的那个,但发现当它正在工作时,另一个线程已移动其干酪,因此Raven抛出并发异常。您的消息将返回队列并重试,现在存在传奇数据,重试就像场景#2。