使用 NSaga 來做 Saga pattern,需先透過 NuGet 安裝 NSaga 套件。

PM> Install-Package NSaga

接著要定義 Transaction 中的每一個動作,也就是 NSaga 中的 Message。

這邊要注意 NSaga 必須特別定義 Transaction 中的第一個 Message,定義的方式就是造一個專屬的 Message Class,並實作 IInitiatingSagaMessage 介面。

public class StartSagaMessage: IInitiatingSagaMessage
{
    public Guid CorrelationId { get; set; }
}

IInitiatingSagaMessage 介面只有一個屬性 CorrelationId,用來接 Transaction Id 用,可用來識別這次觸發的 Transaction。除了介面定義的屬性外,若是需要額外的資料也可以自己附加。

第一個 Message 定義完其它後面的 Message 定義方式就一樣了,只要造專屬的 Message Class,並實作 ISagaMessage 介面即可。

public class SagaMessage: ISagaMessage
{
    public Guid CorrelationId { get; set; }
}

接著要定義 Transaction 要儲存的資訊,簡單造個 Model 類別即可。

public class SagaData
{
    public List Executed { get; } = new List();
}

再來要定義 Transaction,也就是 NSaga 中的 Saga。造一個專屬的 Saga Class,實作 ISaga、InitatedBy、ConsumerOf 介面。

public class Saga: ISaga, InitiatedBy, ConsumerOf
{
    ...
}

Transaction 的 Message 由 InitatedBy、ConsumerOf 介面實作去定義,Transaction的第一個 Message 用 InitatedBy 去實作,其他的 Message 都用 ConsumerOf 介面去實作,對應的實作方法用來定義執行對應 Message 時所要做的動作。

...
public OperationResult Initiate(StartSagaMessage message)
{
    Console.WriteLine(message.GetType().Name);
    SagaData.Executed.Add(message.GetType().Name);

    return new OperationResult();
}
...

像是下面這樣:

public class Saga: ISaga, InitiatedBy, ConsumerOf
{
    public Guid CorrelationId { get; set; }
    public Dictionary Headers { get; set; }
    public SagaData SagaData { get; set; }
    public OperationResult Initiate(StartSagaMessage message)
    {
        Console.WriteLine(message.GetType().Name);
        SagaData.Executed.Add(message.GetType().Name);

        return new OperationResult(); 
    }

    public OperationResult Consume(SagaMessage message)
    {
        Console.WriteLine(message.GetType().Name);
        SagaData.Executed.Add(message.GetType().Name);
        
        return new OperationResult(); 
    }
}

最後開始實際運行,取得 mediator 與 repository。

...
var builder = Wireup.UseInternalContainer();
var mediator = builder.ResolveMediator();
var repository = builder.ResolveRepository();
...

透過 mediator 依序調用 Message 即可。

...
mediator.Consume(new StartSagaMessage()
{
    CorrelationId = correlationId,
});
...

若有需要可從 repository 取出儲存的資料。

...
var saga = repository.Find(correlationId);
...

像是下面這樣:

using System;

namespace NSaga.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = Wireup.UseInternalContainer();
            var mediator = builder.ResolveMediator();
            var repository = builder.ResolveRepository();
            
            var correlationId = Guid.NewGuid();

            mediator.Consume(new StartSagaMessage()
            {
                CorrelationId = correlationId,
            });

            mediator.Consume(new SagaMessage()
            {
                CorrelationId = correlationId
            });

            var saga = repository.Find(correlationId);

            Console.WriteLine(string.Join(", ", saga.SagaData.Executed.ToArray()));
        }
    }
}

1.png