MassTransit传奇/状态机未将请求响应返回到控制器

时间:2018-07-30 07:32:42

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

我觉得我已经很接近完成这项工作了,但似乎无法完全解决问题……

我有一个带有saga / state机器的.NET Core ASP应用程序,在大多数情况下似乎运行良好。它:

  1. 接收请求
  2. 发布由路由清单获取的事件
  3. 路由单完成后,它将发布一个事件
  4. 该事件是传奇故事
  5. 传奇然后将请求发送给请求/响应使用者
  6. 回应又回到了传奇故事
  7. 然后我然后使用RespondAsync尝试将响应发送回原始调用控制器,但是a回没有结果

我的控制器如下:

private readonly IRequestClient<IRequestLink, IRequestLinkCompleted> _client;

public async Task<IActionResult> CreateLinkAsync([FromBody]CreateLinkRequest request)
{
        var requestLink = new RequestLink            {

            GroupName = $"{request.Name} Group",
            Name = request.Name,
            LinkId = Guid.NewGuid()
        };

        var result = await _client.Request(requestLink).ConfigureAwait(false);

        return Ok();
}

我的传奇的简化版本如下:

Request(() => LinkRequest, x => x.RequestId, cfg =>
        {
            cfg.ServiceAddress = new Uri($"rabbitmq://localhost/request_end_point_name");
            cfg.SchedulingServiceAddress = new Uri($"rabbitmq://localhost/request_end_point_name");
            cfg.Timeout = TimeSpan.FromSeconds(30);
        });


During(RequestReceived,
                When(LinkCreatedEvent)
                    .Request(LinkRequest, context => new SelectUrlByPublicId(context.Data.DatabaseId, context.Data.LinkId))
                    .TransitionTo(LinkRequest.Pending));

            During(LinkRequest.Pending,                
                When(LinkRequest.Completed)
                    .ThenAsync(context => context.RespondAsync(new RequestLinkCompleted
                    {
                        CorrelationId = LinkRequest.GetRequestId(context.Instance),
                        DatabaseId = context.Data.DatabaseId
                    }))
                    .Finalize());

最后,在我的启动代码中,我将请求/响应配置为:

services.AddScoped<IRequestClient<IRequestLink, IRequestLinkCompleted>>(x => new MessageRequestClient<IRequestLink, IRequestLinkCompleted>(_bus, new Uri($"{messageBusSettings.Host}/create_link_saga"), TimeSpan.FromSeconds(30)));

我猜想RespondAsync调用没有使用正确的/原始的requestId,但是我不知道如何检查或更改它。有人可以帮忙吗?

2 个答案:

答案 0 :(得分:2)

由于原始请求的上下文丢失了,因此您基本上需要自己RespondAsync中进行操作。

  1. 从请求消息上下文中保存ResponseAddress
  2. 从相同的上下文中保存RequestId

在您的传奇故事中,需要响应时,您需要使用context.GetSendEndpoint(context.Instance.SavedResponseAddress),然后调用Send,在委托中设置RequestId,以匹配原始上下文中保存的RequestId。

现在,您可能需要将它们保存在路由清单变量中,因为您的传奇并没有得到命令,只是后续事件,但是最终效果是一样的,原始请求消息消失了,传奇也从未看到过

答案 1 :(得分:0)

对于那些希望更清晰地了解一些代码片段以使其正常工作的人来说,这是我们为使传奇返回初始请求者的响应而开始并运行的工作。我将上面的答案标记为正确,但是由于我们对sagas还是很陌生,并且无法弄清楚从何处获取原始上下文的信息,因此我们花了几个小时才能使实现工作正常进行我们实现传奇结构的方式。希望这可以节省其他人将来的时间。

初始英雄传奇设置:

 public PaymentStateMachine()
        {
            InstanceState(x => x.CurrentState);

            this.ConfigureCorrelationIds();
.....
//Saga workflow registration
.....

            this.During(ValidationSucceeded
                , SetPaymentGatewaySubmissionHandler());            

            SetCompletedWhenFinalized();
        }

我们必须注册初始事件,以便将初始消息中的某些信息保存到saga表中。要注意的关键部分是保存在Saga Factory中的 context.responseAddress context.RequestId

private void ConfigureCorrelationIds()
{
Event(() => PaymentSubmittedEvent,
                x =>
                {

                    x.CorrelateBy<int>(pay => pay.OrderId, context => context.Message.Order.Id)
                    .SelectId(c => c.Message.CorrelationId);

                    x.InsertOnInitial = true;

                    x.SetSagaFactory(context => new PaymentSagaState()
                    {
                        ResponseAddress = context.ResponseAddress.ToString(),
                        CorrelationId = context.CorrelationId.Value,
                        RequestId = context.RequestId,
                        OrderId = context.Message.Order.Id,
                        Created = DateTime.Now,
                        Updated = DateTime.Now,
                        CurrentState = Initial.Name
                    });
                });
....
//Other events registered
}

然后,在处理完传奇之后,我们使用上面标记的答案将响应发送回原始邮件的响应地址。 new PaymentSubmittedResponse 只是我们在客户端正在等待的消息合同的传奇中的私有实现。

private EventActivityBinder<PaymentSagaState, IPaymentGatewaySubmittedEvent> SetPaymentGatewaySubmissionHandler() =>
            When(PaymentGatewaySubmittedEvent)
                .Then(c => this.UpdateSagaState(c, PaymentGatewayComplete.Name))
                .Then(c => Console.Out.WriteLineAsync(
                    $"{DateTime.Now} PaymentGatewayComplete: {c.Data.Status} to {c.Instance.CorrelationId}"))
                .ThenAsync(async c =>
                {              
                    //Send response back to orignial requestor once we are done with this step               
                    var responseEndpoint =
                            await c.GetSendEndpoint(new Uri(c.Instance.ResponseAddress));

                    await responseEndpoint.Send(new PaymentSubmittedResponse(c.Data), callback: sendContext => sendContext.RequestId = c.Instance.RequestId);
                })
            .TransitionTo(ClientPaymentSubmissionResponded);

private void UpdateSagaState(BehaviorContext<PaymentSagaState> sagaContext, string status)
        {
            sagaContext.Instance.CurrentState = status;
            sagaContext.Instance.Updated = DateTime.Now;
        }

private class PaymentSubmittedResponse : IPaymentSubmittedEvent
        {
            public string Status { get; set; }

            public OrderDto Order { get; set; }

            public Guid CorrelationId { get; set; }
            public DateTime TimeStamp { get; set; }

            public PaymentSubmittedResponse(IPaymentBase paymentMessage)
            {
                Order = paymentMessage.Order;
                CorrelationId = paymentMessage.CorrelationId;
                Status = paymentMessage.Status;
                TimeStamp = paymentMessage.TimeStamp;
            }
        }

不确定是否完全需要它,或者仅仅是由于我们实现它的原因,但是我们不得不引入 ClientPaymentSubmissionResponded 的另一个传奇状态来处理发送回原始消息的响应消息事件请求。