Saga (шаблон проєктування)Saga — патерн проєктування, який дозволяє координувати транзакції здійснені до різних баз даних у вигляді однієї операції. ПроблемаНеобхідно виконати декілька транзакцій до різних баз даних у вигляді єдиної операції. ВирішенняВиконувати транзакції одна за одною, та в разі невдачі хоч однієї скасувати усі попередні. ОписХореографіяКожний вузол здійснює транзакцію та публікує повідомлення, яке запускає транзакції в інших вузлах.
Оркестрація
Недоліки
РеалізаціяC#Приклад реалізації мовою С#
using System;
using System.Collections.Generic;
using System.Linq;
namespace SagaPattern
{
public interface IMessage
{
string SagaId { get; set; }
}
public interface ISaga
{
public string Id { get; }
public void StartSaga();
public bool IsStartOfSaga<TMessage>()
where TMessage : IMessage;
public bool IsEndOfSaga<TMessage>()
where TMessage : IMessage;
public void Handle<TMessage>(TMessage message)
where TMessage : IMessage;
}
public interface IOrchestrator
{
public void RegisterSaga<TSaga>()
where TSaga : ISaga;
public void Send<TMessage>(TMessage message)
where TMessage : IMessage;
}
public class OrderingSaga : ISaga
{
public string Id { get; private set; }
public void StartSaga()
{
Id = Guid.NewGuid().ToString();
}
public bool IsStartOfSaga<TMessage>()
where TMessage : IMessage
{
return typeof(TMessage) == typeof(UserCreateAnOrder);
}
public bool IsEndOfSaga<TMessage>()
where TMessage : IMessage
{
var messageType = typeof(TMessage);
return messageType == typeof(ProductSaved) || messageType == typeof(OrderRemoved);
}
public void Handle<TMessage>(TMessage message)
where TMessage : IMessage
{
if (IsStartOfSaga<TMessage>() || message.SagaId == Id)
{
Handle((dynamic)message);
}
}
public void Handle(UserCreateAnOrder message)
{
Console.WriteLine("User create an order");
Orchestrator.Instance.Send(new SaveOrder
{
SagaId = Id
});
}
public void Handle(SaveOrder message)
{
. . .
}
. . .
}
public class UserCreateAnOrder : IMessage
{
public string SagaId { get; set; }
}
public class OrderRemoved : IMessage { . . . }
public class SaveOrder : IMessage { . . . }
public class OrderSaved : IMessage { . . . }
public class RemoveOrder: IMessage { . . . }
public class SaveProduct : IMessage { . . . }
public class FailToSaveProduct : IMessage { . . . }
public class ProductSaved : IMessage { . . . }
public class Orchestrator : IOrchestrator
{
public static Orchestrator Instance { get; } = new Orchestrator();
private readonly ICollection<ISaga> _sagas = new List<ISaga>();
private readonly IDictionary<string, ISaga> _activeSagas = new Dictionary<string, ISaga>();
public void RegisterSaga<TSaga>()
where TSaga: ISaga
{
var saga = Activator.CreateInstance<TSaga>();
_sagas.Add(saga);
}
public void Send<TMessage>(TMessage message)
where TMessage : IMessage
{
if (DoesMessageStartAnyOfSaga<TMessage>())
{
StartSaga<TMessage>();
}
if (IsMessageAPartOfAnyActiveSaga(message))
{
HandleMessage(message);
}
if (DoesMessageEndAnyOfActiveSaga(message))
{
EndSaga(message);
}
}
#region StartSaga
private bool DoesMessageStartAnyOfSaga<TMessage>()
where TMessage : IMessage
{
return _sagas.Any(s => s.IsStartOfSaga<TMessage>());
}
private void StartSaga<TMessage>()
where TMessage : IMessage
{
var saga = _sagas.Single(s => s.IsStartOfSaga<TMessage>());
saga.StartSaga();
_activeSagas.Add(saga.Id, saga);
}
#endregion
#region HandleMessage
private bool IsMessageAPartOfAnyActiveSaga<TMessage>(TMessage message)
where TMessage : IMessage
{
if (DoesMessageStartAnyOfSaga<TMessage>())
{
return _activeSagas.Values.Any(s => s.IsStartOfSaga<TMessage>());
}
else
{
return _activeSagas.ContainsKey(message.SagaId);
}
}
private void HandleMessage<TMessage>(TMessage message)
where TMessage : IMessage
{
var saga = GetSagaForMessage(message);
saga.Handle(message);
}
private ISaga GetSagaForMessage<TMessage>(TMessage message)
where TMessage : IMessage
{
if (DoesMessageStartAnyOfSaga<TMessage>())
{
return _activeSagas.Values.Single(s => s.IsStartOfSaga<TMessage>());
}
else
{
return _activeSagas[message.SagaId];
}
}
#endregion
#region EndSage
private bool DoesMessageEndAnyOfActiveSaga<TMessage>(TMessage message)
where TMessage : IMessage
{
if (string.IsNullOrWhiteSpace(message.SagaId)) return false;
var saga = _activeSagas[message.SagaId];
return saga.IsEndOfSaga<TMessage>();
}
public void EndSaga<TMessage>(TMessage message)
where TMessage : IMessage
{
_activeSagas.Remove(message.SagaId);
}
#endregion
}
class Program
{
static void Main(string[] args)
{
Orchestrator.Instance.RegisterSaga<OrderingSaga>();
// розпочинаємо сагу
Orchestrator.Instance.Send(new UserCreateAnOrder());
Console.WriteLine("Hello World!");
}
}
}
Див. також |