Độ tin cậy Nâng cao 5 phút đọc

Pattern saga: transaction phân tán không cần 2PC

Cách phối hợp workflow nghiệp vụ nhiều bước xuyên service trong .NET. Choreography vs orchestration, MassTransit saga, và compensation event hoàn tác.

Mục lục
  1. Khi nào saga thành không tránh khỏi?
  2. Số nào và trade-off nào nên ngân sách?
  3. Hình dạng saga trông thế nào?
  4. Cấu hình .NET 10 với MassTransit Saga State Machine?
  5. Saga tạo failure mode nào?
  6. Khi nào saga là quá liều?
  7. Đi tiếp đâu từ đây?

Bài toán reliability khó nhất trong microservice là giữ data nhất quán xuyên service mà không có distributed transaction. Saga là câu trả lời ngành công nghiệp rút gọn thành - chuỗi transaction local cộng bước undo bù trừ. Chương này cho thấy triển khai saga trong .NET với MassTransit, khi nào chọn choreography vs orchestration, và failure mode quyết định mỗi cái.

Khi nào saga thành không tránh khỏi?

Ba tín hiệu.

Workflow vượt ranh giới service. Đặt hàng chạm Inventory, Payment, Shipping - mỗi bên sở hữu database. Không bọc được vào một transaction; 2PC dễ vỡ vận hành và gần như không managed service nào hỗ trợ.

Lỗi cục bộ không có thể chấp nhận. Trừ thẻ mà không giữ inventory là email refund-and-apologise; giữ inventory mà không trừ thẻ là mất doanh thu. Hệ phải kết thúc ở "tất cả xong" hoặc "tất cả hoàn tác".

Bước chậm hoặc bất đồng bộ. Gửi yêu cầu fulfilment đến kho third-party mất nhiều giờ; transaction đồng bộ thậm chí không khả thi. Transaction theo bước của saga gỡ ghép thời gian.

Nếu workflow của bạn nằm trọn trong một database, dùng một transaction EF Core và bỏ chương này.

Số nào và trade-off nào nên ngân sách?

Thuộc tính                Choreography           Orchestration
Coupling                  lỏng (event)            chặt hơn (coordinator)
Hiển thị                  trải qua các service    một log
Thêm bước                 đổi >= 2 service        đổi orchestrator
Phức tạp debug            cao (mạng event)        thấp (state machine)
Tự trị team               cao                     trung bình

Phần lớn team .NET nên bắt đầu orchestration. "Một state machine đọc được" thắng "mạng event phi tập trung" cho hệ thống vừa. Lên cấp choreography khi team service đủ lớn để phối hợp đổi orchestrator thành điểm nghẽn.

Hình dạng saga trông thế nào?

Orchestration với coordinator rõ ràng:

sequenceDiagram
    participant C as Client
    participant S as OrderSaga
    participant I as Inventory
    participant P as Payment
    participant Sh as Shipping
    C->>S: PlaceOrder
    S->>I: ReserveInventory
    I-->>S: Reserved
    S->>P: ChargeCard
    P-->>S: Charged
    S->>Sh: Ship
    Sh-->>S: Shipped
    S-->>C: OrderCompleted

Nếu ChargeCard fail, saga phát ReleaseInventory để bù. Nếu Ship fail, phát cả RefundCardReleaseInventory. Orchestrator sở hữu luật.

Cấu hình .NET 10 với MassTransit Saga State Machine?

public class OrderSagaState : SagaStateMachineInstance
{
    public Guid CorrelationId { get; set; }
    public string CurrentState { get; set; } = "";
    public Guid OrderId { get; set; }
    public Guid UserId { get; set; }
    public decimal Amount { get; set; }
    public Guid? ReservationId { get; set; }
    public Guid? ChargeId { get; set; }
}

public class OrderSaga : MassTransitStateMachine<OrderSagaState>
{
    public State Reserving { get; private set; } = null!;
    public State Charging { get; private set; } = null!;
    public State Shipping { get; private set; } = null!;
    public State Completed { get; private set; } = null!;
    public State Failed { get; private set; } = null!;

    public Event<PlaceOrder> Started { get; private set; } = null!;
    public Event<InventoryReserved> InventoryReserved { get; private set; } = null!;
    public Event<InventoryReservationFailed> InventoryFailed { get; private set; } = null!;
    public Event<PaymentCharged> PaymentCharged { get; private set; } = null!;
    public Event<PaymentChargeFailed> PaymentFailed { get; private set; } = null!;

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

        Initially(
            When(Started)
                .Then(ctx => { ctx.Saga.OrderId = ctx.Message.OrderId;
                                ctx.Saga.Amount = ctx.Message.Amount; })
                .Publish(ctx => new ReserveInventory(ctx.Saga.OrderId, ctx.Message.Items))
                .TransitionTo(Reserving));

        During(Reserving,
            When(InventoryReserved)
                .Then(ctx => ctx.Saga.ReservationId = ctx.Message.ReservationId)
                .Publish(ctx => new ChargeCard(ctx.Saga.OrderId, ctx.Saga.Amount))
                .TransitionTo(Charging),
            When(InventoryFailed)
                .TransitionTo(Failed));

        During(Charging,
            When(PaymentCharged)
                .Publish(ctx => new ShipOrder(ctx.Saga.OrderId))
                .TransitionTo(Shipping),
            When(PaymentFailed)
                .Publish(ctx => new ReleaseInventory(ctx.Saga.ReservationId!.Value))  // bù
                .TransitionTo(Failed));

        // ... state Shipping, Completed lược
    }
}

Ba chi tiết. State machine tài liệu của workflow; engineer mới đọc và hiểu hệ. Persistence vào EF Core là đăng ký một dòng; khi crash, orchestrator resume từ state cuối đã persist. Compensation chỉ là publish event khác - cùng hạ tầng MassTransit xử lý nó.

Saga tạo failure mode nào?

Chương 13 emit event chuyển state saga thành trace OpenTelemetry - cả workflow là một Jaeger span.

Khi nào saga là quá liều?

Ba mùi.

Một: một write database giả dạng workflow. Nếu "bước" đều nằm trong một DbContext, transaction là hình đúng, không phải saga.

Hai: workflow đồng bộ thời gian chặt. Nếu user chờ kết quả và mỗi bước phải xong trong mili giây, eventual consistency của saga là mô hình sai. Chuỗi gọi trực tiếp (với handler resilience chương 11) đơn giản hơn.

Ba: khi compensation bất khả thi. "Gửi thông báo" không có nghịch đảo - không un-send được. Nếu workflow chứa bước không thể bù, sắp xếp lại để chúng chạy cuối (không bước sau nào fail) hoặc chấp nhận chúng có thể chạy trong state lỗi.

Đi tiếp đâu từ đây?

Bạn đã hoàn tất nhóm reliability. Chương kế tiếp: Observability OpenTelemetry trong .NET - metric, trace, log cho phép thấy saga, breaker, outbox của bạn có thực sự chạy trên production không.

Câu hỏi thường gặp

Khi nào saga đáng phức tạp?
Khi có workflow nghiệp vụ vượt nhiều service và cần all-or-nothing, nhưng two-phase commit (2PC) không khả dụng. Ví dụ: đặt hàng → giữ inventory → trừ thẻ → giao hàng. Mỗi bước nằm ở service khác; nếu trừ thẻ fail, phải release inventory. Saga là câu trả lời thực dụng duy nhất ở quy mô.
Choreography hay orchestration?
Orchestration khi workflow phức tạp, nhiều bước, hay đổi - một chỗ đọc trọn chuỗi. Choreography khi service được sở hữu bởi team khác nhau và bạn muốn coupling lỏng - mỗi service publish event và subscribe cái khác. Phần lớn hệ .NET production bắt đầu orchestrated và lên cấp choreography chỉ khi quy mô team ép.
Compensating transaction là gì?
Là undo của bước. ChargeCard → RefundCard. ReserveInventory → ReleaseInventory. Compensation không phải rollback database - transaction gốc đã commit. Compensation là thao tác nghiệp vụ mới đưa hệ về state nhất quán. Ngữ nghĩa quan trọng: refund không giống chưa từng charge, và user có thể thấy cả hai.
Saga idempotent ra sao?
Mỗi handler bước phải idempotent - xử lý ở chương 10. Mỗi compensation cũng phải idempotent. State machine saga cần persist để orchestrator crash có thể resume từ bước cuối biết. State machine của MassTransit persist vào DB (Entity Framework hoặc Redis) và xử lý crash recovery tự động.