使用saga事件对在使用者中发布的消息做出反应

时间:2019-03-08 20:56:03

标签: c# asp.net-core masstransit automatonymous

我正在使用MassTransit,RabbitMq和Automatonymous整合概念验证 一个asp.net核心2.1应用程序。我正在使用EntityFramework核心和Postgres 坚持不懈。

我想做的是在 向http rest api发出请求,并在传奇完成后返回结果。 我正在做的是:

  • 使用具有请求/响应客户端的界面,通过一个事件来启动我的传奇
  • 传奇中的
  • 发布一条消息,供消费者使用
  • 消费者中的
  • 发布与我的传奇中的另一个事件相对应的消息
  • 完成后返回我的传奇故事的回复并完成

这是我的代码:

我的界面

public interface IStartSagaRequest
{
    Guid CorrelationId { get; set; }
    string Name {get; set;}
}

public interface IStartSagaResponse
{
    Guid CorrelationId { get; set; }
    bool DidComplete {get; set;}
}

public IDoOperationRequest
{
    Guid CorrelationId { get; set; }
}

public IOperationComplete
{
    Guid CorrelationId { get; set; }
    bool OperationSuccessful {get; set;}
}

我的传奇实例

public class DoOperationSaga : SagaStateMachineInstance
{
    public Guid CorrelationId { get; set; }
    public Name { get; set; }
    public string CurrentState { get; set; }
}

用于在状态机中发布的IDoOperationRequest的具体实现

public class DoOperationRequestImpl : IDoOperationRequest
{
    public Guid CorrelationId { get; set; }
}

用于在状态机中发布的IStartSagaResponse的具体实现

public class StartSagaResponse : IStartSagaResponse
{
    public Guid CorrelationId { get; set; }
    public bool DidComplete {get; set;}
}

我的状态机

public class ProcessOperationStateMachine : MassTransitStateMachine<DoOperationSaga>
{
    public State OperationPending { get; private set; }
    public State Complete { get; private set; }


    public Event<IOperationComplete> OperationCompleteEvent { get; private set; }
    public Event<IStartSagaRequest> StartSagaRequestEvent { get; private set; }


    public ProcessOperationStateMachine()
    {
        InstanceState(doOperationSagaInstance => doOperationSagaInstance.CurrentState);

        Event(() => StartSagaRequestEvent, eventConfigurator =>
        {
            eventConfigurator.CorrelateById(doOperationSaga => doOperationSaga.CorrelationId,
                    context => context.Message.CorrelationId).SelectId(c => Guid.NewGuid());
        });

        Event(() => OperationCompleteEvent, eventConfigurator =>
        {
            eventConfigurator.CorrelateById(doOperationSaga => doOperationSaga.CorrelationId,
                context => context.Message.CorrelationId);
        });


        Initially(
            When(StartSagaRequestEvent)
                .Then(context =>
                {
                    context.Instance.CorrelationId = context.Data.CorrelationId;
                    context.Instance.Name = context.Data.Name;
                    context.Publish(new DoOperationRequestImpl
                    {
                        CorrelationId = context.Data.CorrelationId
                    });

                })
                .TransitionTo(OperationPending)
        );

        During(OperationPending,
            When(OperationCompleteEvent)
                .Then(context =>
                {
                    // I'm just doing this for debugging
                    context.Instance.Name = "changed in operationComplete";
                })
                .ThenAsync(context => context.RespondAsync(new StartSagaResponse 
                { 
                    CorrelationId = context.Data.CorrelationId,
                    DidComplete = true
                }))
                .Finalize());

}

我的消费者:

public class DoOperationRequestConsumer : IConsumer<ISBDoOperationRequest>
{

    public async Task Consume(ConsumeContext<ISBDoOperationRequest> context)
    {
       await context.Publish<IOperationComplete>(new
       {
          CorrelationId = context.Message.CorrelationId,
          OperationSuccessful = true
       });
    }
}

我如何在Startup.cs的DI中进行连接

public void ConfigureServices(IServiceCollection services)
{
    stateMachine = new ProcessOperationStateMachine();

    SagaDbContextFactory factory = new SagaDbContextFactory();
    EntityFrameworkSagaRepository<DoOperationSaga> repository = new EntityFrameworkSagaRepository<DoOperationSaga>(factory);

    services.AddMassTransit(x =>
    {

        x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(sbc =>
        {
            IRabbitMqHost host = sbc.Host(new Uri("rabbitmq://localhost/"), h =>
            {
                h.Username("guest");
                h.Password("guest");
            });

            sbc.ReceiveEndpoint(host, "do-operation", ep =>
            {
                ep.UseMessageRetry(c => c.Interval(2, 100));
                ep.StateMachineSaga(stateMachine, repository);
                ep.Durable = false;
            });

            sbc.ReceiveEndpoint(host, "consumer-queue", ep =>
            {
                ep.Consumer(() => new DoOperationRequestConsumer());
                ep.Durable = false;
            });
        }));
        x.AddConsumer<DoOperationRequestConsumer>();
    });

    services.AddScoped<DoOperationRequestConsumer>();

    services.AddScoped(p =>
        p.GetRequiredService<IBusControl>()
            .CreateRequestClient<IDoOperationRequest, IDoOperationResponse>(
                new Uri("rabbitmq://localhost/do-operation?durable=false"),
                TimeSpan.FromSeconds(30)));

}

并在我的控制器中发出请求:

public IRequestClient<IDoOperationRequest, IDoOperationResponse> _doOperationClient { get; set; }
...
var guid = Guid.NewGuid();
_doOperationClient.Request(new
{
    Name = "from the controller",
    CorrelationId = guid
});

我看到的是我的状态机确实启动了。什么时候(StartSagaRequestEvent)被击中 并且DoOperationRequest消息被发布。 DoOperationRequestConsumer确实收到消息 并发布IOperationComplete消息。但是,这就是停止的地方。我的IOperationCompleteEvent 在我的状态机不被调用。当我查看数据库时,我可以看到我的传奇实例得到 用guid创建,并且CurrentState设置为OperationPending。当我看着我的兔子 管理仪表板我看到消息在DoOperationRequestConsumer完成后发布 IOperationComplete消息发布。我只是没有看到状态机消耗IOperationComplete 消费者正在发布的消息。当我设置断点并检查使用者中的消息时 我确实看到将CorrelationId设置为传奇的CorrelationId的相同值。

我还尝试在 消费者:

public async Task Consume(ConsumeContext<ISBDoOperationRequest> context)
{
    ISendEndpoint sendEndpoint = await context.GetSendEndpoint(new Uri("rabbitmq://localhost/do-operation?durable=false"));

    await sendEndpoint.Send<IOperationComplete>(new
    {
      CorrelationId = context.Message.CorrelationId,
      OperationSuccessful = true
    });
}

,但仍然无法建立连接。

我整天都在撞我的头,不确定什么 我在这里不见了。如果有人能就我可能做错的事给我一些建议,我将不胜感激 它,再次对文本的墙感到抱歉,我知道可以阅读,但是我想清楚自己在做什么。 非常感谢!

1 个答案:

答案 0 :(得分:2)

您的事件相关性ID似乎是可疑的,应该是这样的:

Event(() => StartSagaRequestEvent, eventConfigurator =>
{
    eventConfigurator.CorrelateById(context => context.Message.CorrelationId)
        .SelectId(context => context.Message.CorrelationId);
});

那样,它将初始化为消息的CorrelationId。

无关,但是您的端点应该对容器使用扩展方法:

sbc.ReceiveEndpoint(host, "consumer-queue", ep =>
{
    ep.ConfigureConsumer<DoOperationRequestConsumer>();
    ep.Durable = false;
});

并通过在扩展中对其进行配置来使用新的请求客户端。

x.AddRequestClient<IDoOperationRequest>(new Uri("rabbitmq://localhost/do-operation?durable=false"));

此外,在您的初始状态下,应删除此行:

context.Instance.CorrelationId = context.Data.CorrelationId;