在saga处理程序使用NServiceBus

时间:2017-03-30 15:55:39

标签: nservicebus

使用NServiceBus(v6),有没有办法确保在触发消息的Saga Handler之前在SagaData对象中设置属性?

我们的环境是多租户的,所以我想确保将正确的CustomerId用于数据库访问等。并且开发人员不要忘记从传入的消息/消息头中提取此值。

例如,考虑到这个传奇数据...

public interface ICustomerSagaData : IContainSagaData
{
    Guid CustomerId { get; set; }
}

public class SomeProcessSagaData : ICustomerSagaData
{
    // IContainSagaData and other properties removed for brevity ...

    #region ICustomerSagaData properties

    public virtual Guid CustomerId { get; set; }

    #endregion
}

......以及以下的传奇...

public class  SomeProcessSagaSaga :
    Saga<SomeProcessSagaData>,
    IAmStartedByMessages<StartProcess>
{
    public async Task Handle(StartProcess message, IMessageHandlerContext context)
    {
        // How do I ensure that Data.CustomerId is already set at this point?

    }

    // ConfigureHowToFindSaga etc ...
}

我最初尝试在管道中插入一个行为,例如

public class MyInvokeHandlerBehavior : Behavior<IInvokeHandlerContext>
{
    public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
    {
        // Ideally I'd like to set the CustomerId here before the 
        // Saga Handler is invoked but calls to ...     
        //      context.Extensions.TryGet(out activeSagaInstance);
        // return a null activeSagaInstance

        await next().ConfigureAwait(false);

        // This is the only point I can get the saga data object but
        //  as mentioned above the hander has already been invoked
        ActiveSagaInstance activeSagaInstance;
        if (context.Extensions.TryGet(out activeSagaInstance))
        {
            var instance = activeSagaInstance.Instance.Entity as ICustomerSagaData;
            if (instance != null)
            {
                Guid customerId;
                if (Guid.TryParse(context.Headers["CustomerId"), out customerId))
                {
                    instance.CustomerId = customerId; 
                }
            }
        }
    }
}

...但这只允许在处理程序被触发后访问SagaData实例

2 个答案:

答案 0 :(得分:1)

迟到的答案,但你需要确保你的行为在SagaPersistenceBehavior之后执行。

在您的IConfigureThisEndpoint实现中:

    public virtual void Customize(EndpointConfiguration configuration)
    {
        configuration.Pipeline.Register<Registration>();
    }

    public class Registration : RegisterStep
    {
        public Registration()
            : base(
                stepId: "AuditMutator",
                behavior: typeof(AuditMutator),
                description: "Sets up for auditing")
        {
            this.InsertAfterIfExists("InvokeSaga");
        }
    }

答案 1 :(得分:0)

因此,在处理StartProcess消息时,不会直接回答您的问题Data.CustomerId。您需要使用消息中的ID设置它。

public async Task Handle(StartProcess message, IMessageHandlerContext context)
{
    Data.CustomerId = message.CustomerId;
}

据说上面的示例缺少一个关键部分,这是确定如何查找传奇以继续处理的代码:

protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SomeProcessSagaData> mapper)
{
    mapper.ConfigureMapping<StartProcess>(message => message.CustomerId)
        .ToSaga(sagaData => sagaData.CustomerId);
}

每次发送由saga处理的消息类型时,您需要配置ConfigureHowToFindSaga()方法,以便它可以查找以前启动的saga以继续处理。因此,从本质上讲,您将使用StartProcess消息为您发送的每个客户ID 开始一个新的传奇。您可以在此处详细了解:https://docs.particular.net/nservicebus/sagas/

所以现在真正的问题是你真的需要在这一点上使用传奇吗?该示例似乎只处理一种类型的消息,所以您真的需要保存CustomerId的状态吗?你的样本中没有必要使用saga的开销,我相信基于上面的例子,常规处理程序就可以了。