Rebus Sagas,Revisions和DeferredMessages

时间:2018-05-21 12:33:57

标签: rebus saga

我试图配置以下列方式工作的Saga:

  1. Saga收到装运订单消息。该装运单有一个RouteId属性,我可以使用该属性来关联相同的卡车"
  2. 的装运单。
  3. 这些装运单由另一个系统创建,可以使用批处理发送此订单。但是,该系统无法对同一地址的装运单进行分组。
  4. 经过一段时间后,我只使用此RouteId发送了另一条消息。我需要获取收到的RouteId的所有运输订单,按地址分组,然后将其转换为另一个对象并发送到另一个Web服务。
  5. 但我面临两个问题:

    1. 如果我同时发送两条消息""对于第一个处理程序,每个处理程序,甚至具有与该消息相关的属性,在处理第一个消息后IsNew属性不会更改
    2. 在第二个处理程序中,我希望访问与那些Saga相关的所有数据,但我不能,因为数据似乎是数据,因为它们在这些消息的修订中被推迟了。
    3. 相关守则:

      传奇的总线配置

      Bus = Configure.With(Activator)
         .Transport(t => t.UseRabbitMq(rabbitMqConnectionString, inputQueueName))
         .Logging(l => l.ColoredConsole())
         .Routing(r => r.TypeBased().MapAssemblyOf<IEventContract(publisherQueue))
         .Sagas(s => {
             s.StoreInSqlServer(connectionString, "Sagas", "SagaIndex");
                if (enforceExclusiveAccess)
                {
                    s.EnforceExclusiveAccess();
                }
             })
         .Options(o =>
             {
               if (maxDegreeOfParallelism > 0)
               {
                  o.SetMaxParallelism(maxDegreeOfParallelism);
               }
               if (maxNumberOfWorkers > 0)
               {
                  o.SetNumberOfWorkers(maxNumberOfWorkers);
               }
            })
         .Timeouts(t => { t.StoreInSqlServer(dcMessengerConnectionString, "Timeouts"); })
         .Start();
      

      SagaData类:

      public class RouteListSagaData : ISagaData
      {
          public Guid Id { get; set; }
          public int Revision { get; set; }
      
          private readonly IList<LisaShippingActivity> _shippingActivities = new List<LisaShippingActivity>();
      
          public long RoutePlanId { get; set; }
      
          public IEnumerable<LisaShippingActivity> ShippingActivities => _shippingActivities;
          public bool SentToLisa { get; set; }
      
          public void AddShippingActivity(LisaShippingActivity shippingActivity)
          {
              if (!_shippingActivities.Any(x => x.Equals(shippingActivity)))
              {
                  _shippingActivities.Add(shippingActivity);
              }
          }
      
          public IEnumerable<LisaShippingActivity> GroupShippingActivitiesToLisaActivities() => LisaShippingActivity.GroupedByRouteIdAndAddress(ShippingActivities);
      }
      

      CorrelateMessages方法

      protected override void CorrelateMessages(ICorrelationConfig<RouteListSagaData> config)
      {
          config.Correlate<ShippingOrder>(x => x.RoutePlanId, y => y.RoutePlanId);
          config.Correlate<VerifyRouteListIsComplete>(x => x.RoutePlanId, y => y.RoutePlanId);
      }
      

      处理用于启动Saga的消息并发送DefferedMessage如果saga IsNew

      public async Task Handle(ShippingOrder message)
      {
        try
        {
          var lisaActivity = message.AsLisaShippingActivity(_commissionerUserName);
      
          if (Data.ShippingActivities.Contains(lisaActivity))
            return;
      
          Data.RoutePlanId = message.RoutePlanId;
          Data.AddShippingActivity(lisaActivity);
          var delay = TimeSpan.FromSeconds(_lisaDelayedMessageTime != 0 ? _lisaDelayedMessageTime : 60);
      
          if (IsNew)
          {
            await _serviceBus.DeferLocal(delay, new VerifyRouteListIsComplete(message.RoutePlanId), _environment);
          }
       }
       catch (Exception err)
       {
         Serilog.Log.Logger.Error(err, "[{SagaName}] - Error while executing Route List Saga", nameof(RouteListSaga));
         throw;
       }
      }
      

      最后,deffered消息的处理程序:

      public Task Handle(VerifyRouteListIsComplete message)
      {
        try
        {
          if (!Data.SentToLisa)
          {
            var lisaData = Data.GroupShippingActivitiesToLisaActivities();
      
            _lisaService.SyncRouteList(lisaData).Wait();
      
            Data.SentToLisa = true;
          }
          MarkAsComplete();
          return Task.CompletedTask;
        }
        catch (Exception err)
        {
          Serilog.Log.Error(err, "[{SagaName}] - Error sending message to LisaApp. RouteId: {RouteId}", nameof(RouteListSaga), message.RoutePlanId);
          _serviceBus.DeferLocal(TimeSpan.FromSeconds(5), message, _configuration.GetSection("AppSettings")["Environment"]).Wait();
          MarkAsUnchanged();
          return Task.CompletedTask;
        }
      }
      

      感谢任何帮助!

1 个答案:

答案 0 :(得分:1)

我不确定我是否正确理解你正在经历的症状。

  

如果我“同时”向第一个处理程序发送两条消息,每个消息都会出现,甚至包含与该消息相关的属性,则在处理完第一条消息后,IsNew属性不会更改

如果调用EnforceExclusiveAccess,我希望以串行方式处理消息,第一个消息IsNew == true,第二个消息IsNew == false

如果没有,我希望这两个消息与IsNew == true并行处理,但是 - 当插入sage数据时 - 我希望其中一个成功,另一个失败,{ {1}}。

ConcurrencyException之后,将再次处理该邮件,这次是ConcurrencyException

这不是你所经历的吗?

  

在第二个处理程序中,我希望访问与那些Saga相关的所有数据,但我不能,因为数据似乎是数据,因为它们在这些消息的修订中被推迟了。

你是说saga数据中的数据似乎处于延迟IsNew == false消息时的状态?

这听起来很奇怪,你也许不太可能再试一次,看看它是不是真的如此?

更新:我发现了为什么你遇到这种奇怪的行为:你不小心设置了你的传奇处理程序实例,以便在消息中重复使用。

你是这样注册的(警告:不要这样做!):

VerifyRouteListIsComplete

然后_sagaHandler = new ShippingOrderSagaHandler(_subscriber); _subscriber.Subscribe<ShippingOrderMessage>(_sagaHandler); _subscriber.Subscribe<VerifyRoutePlanIsComplete>(_sagaHandler); 方法在Subscribe上进行此调用(警告:不要这样做!):

BuiltinHandlerActivator

这就是为什么这很糟糕(特别是对于一个传奇处理程序),因为处理程序实例本身是有状态的 - 它有一个activator.Register(() => handlerInstance); 属性,包含进程的当前状态,并且还包含{{ 1}}属性。

您应该始终做的是确保每次收到消息时都会创建一个新的处理程序实例 - 您的代码应该更改为:

Data

如果将IsNew的实现更改为:

,则可以执行此操作
_subscriber.Subscribe<ShippingOrderMessage>(() => new ShippingOrderSagaHandler(_subscriber)).Wait();
_subscriber.Subscribe<VerifyRoutePlanIsComplete>(() => new ShippingOrderSagaHandler(_subscriber)).Wait();

这将解决您的独家访问问题:)

您的代码还有另一个问题:您在注册处理程序和启动订阅者总线实例之间存在潜在的竞争条件,因为理论上您可能不幸并且在总线启动和注册处理程序之间开始接收消息。

您应该更改代码以确保在启动总线之前注册所有处理程序(从而开始接收消息)。