NetCore微服務實現事務一致性masstransit之saga使用

星仔007發表於2022-03-31

demo如下,一個訂單處理的小例子:

首先看看結果很簡單:

核心程式碼如下:

using MassTransit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OrderProcessor.Event;
using ServiceModel;
using ServiceModel.Command;
using ServiceModel.DTO;
using ServiceModel.Event;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OrderProcessor.Service
{
    public class OrderProcessorStateMachine:MassTransitStateMachine<ProcessingOrderState>
    {
        private readonly ILogger<OrderProcessorStateMachine> logger;

        public OrderProcessorStateMachine()
        {
            this.logger = GlobalServiceProvider.Instance.CreateScope().ServiceProvider.GetService<ILogger<OrderProcessorStateMachine>>();
            this.InstanceState(x => x.State);
            this.State(() => this.Processing);
            this.ConfigureCorrelationIds();
            this.Initially(this.SetOrderSummitedHandler());
            this.During(Processing, this.SetStockReservedHandler(), SetPaymentProcessedHandler(), SetOrderShippedHandler());
            SetCompletedWhenFinalized();
        }

        private void ConfigureCorrelationIds()
        {
            this.Event(() => this.OrderSubmitted, x => x.CorrelateById(c => c.Message.CorrelationId).SelectId(c => c.Message.CorrelationId));
            this.Event(() => this.StockReserved, x => x.CorrelateById(c => c.Message.CorrelationId));
            this.Event(() => this.PaymentProcessed, x => x.CorrelateById(c => c.Message.CorrelationId));
            this.Event(() => this.OrderShipped, x => x.CorrelateById(c => c.Message.CorrelationId));
        }

        private EventActivityBinder<ProcessingOrderState, IOrderSubmitted> SetOrderSummitedHandler() =>
            When(OrderSubmitted).Then(c => this.UpdateSagaState(c.Instance, c.Data.Order))
                                .Then(c => this.logger.LogInformation($"Order submitted to {c.Data.CorrelationId} received"))
                                .ThenAsync(c => this.SendCommand<IReserveStock>("rabbitWarehouseQueue", c))
                                .TransitionTo(Processing);


        private EventActivityBinder<ProcessingOrderState, IStockReserved> SetStockReservedHandler() =>
            When(StockReserved).Then(c => this.UpdateSagaState(c.Instance, c.Data.Order))
                               .Then(c => this.logger.LogInformation($"Stock reserved to {c.Data.CorrelationId} received"))
                               .ThenAsync(c => this.SendCommand<IProcessPayment>("rabbitCashierQueue", c));


        private EventActivityBinder<ProcessingOrderState, IPaymentProcessed> SetPaymentProcessedHandler() =>
            When(PaymentProcessed).Then(c => this.UpdateSagaState(c.Instance, c.Data.Order))
                                  .Then(c => this.logger.LogInformation($"Payment processed to {c.Data.CorrelationId} received"))
                                  .ThenAsync(c => this.SendCommand<IShipOrder>("rabbitDispatcherQueue", c));


        private EventActivityBinder<ProcessingOrderState, IOrderShipped> SetOrderShippedHandler() =>
            When(OrderShipped).Then(c =>
            {
                this.UpdateSagaState(c.Instance, c.Data.Order);
                c.Instance.Order.Status = Status.Processed;
            })
                              .Publish(c => new OrderProcessed(c.Data.CorrelationId, c.Data.Order))
                              .Finalize();

        private void UpdateSagaState(ProcessingOrderState state, Order order)
        {
            var currentDate = DateTime.Now;
            state.Created = currentDate;
            state.Updated = currentDate;
            state.Order = order;
        }

        private async Task SendCommand<TCommand>(string endpointKey, BehaviorContext<ProcessingOrderState, IMessage> context)
            where TCommand : class, IMessage
        {
            var sendEndpoint = await context.GetSendEndpoint(new Uri(""));
            await sendEndpoint.Send<TCommand>(new
            {
                CorrelationId = context.Data.CorrelationId,
                Order = context.Data.Order
            });
        }
        public  State Processing { get; private set; }
        public Event<IOrderSubmitted> OrderSubmitted { get; private set; }
        public Event<IOrderShipped> OrderShipped { get; set; }
        public Event<IPaymentProcessed> PaymentProcessed { get; private set; }
        public Event<IStockReserved> StockReserved { get; private set; }
        
    }
}
using MassTransit;
using MassTransit.MongoDbIntegration.Saga;
using OrderProcessor;
using OrderProcessor.Service;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

builder.Services.AddMassTransit(x =>
{
    x.UsingRabbitMq((context, cfg) =>
    {
        var connection = "amqp://lx:admin@ip:5672/my_vhost";//不加主機會報錯
        cfg.Host(connection);
        cfg.UseDelayedRedelivery(r => r.Intervals(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30)));
        cfg.UseMessageRetry(r => r.Immediate(5));

        cfg.ConfigureEndpoints(context);
        cfg.ReceiveEndpoint("", ep =>
        {
            ep.StateMachineSaga(new OrderProcessorStateMachine(), MongoDbSagaRepository<ProcessingOrderState>.Create("connecturl","db"));
        });

    });
});

var app = builder.Build();

app.Run();

這是整個訂單的幾個步驟。

想把程式碼都貼出來,過程梳理給大家參考,但是時間有限這個點沒那麼多了,而且我理應要把這個程式跑起來的。明天照常上班,暫不過多研究。

整個demo程式碼:

exercise/MassTransitDemo/MassTransitSagasDemo at master · liuzhixin405/exercise (github.com)

 

有興趣可以還有一個demo:

exercise/MassTransitDemo/SagaTest-master at master · liuzhixin405/exercise (github.com)

masstransit官網:

MassTransit (masstransit-project.com)

不得不說這個東西真的很不錯,不過暫時沒找到翻譯,大概的過了下文件,還有好多不清楚的,英文水平有限。demo都是來自外國大佬貢獻的,很遺憾國內有這方面的文章,但是深入一點的都是國外友人的貢獻,而且現成的微服務demo寫的很好很多,視情況專案可借鑑。

 此demo有待後續完善,或大佬幫忙補充後,再完整這個隨筆的流程和程式碼,今天只是起個頭。

相關文章