Saga (шаблон проєктування) — Вікіпедія
Saga — патерн проєктування, який дозволяє координувати транзакції здійснені до різних баз даних у вигляді однієї операції.
Необхідно виконати декілька транзакцій до різних баз даних у вигляді єдиної операції.
Виконувати транзакції одна за одною, та в разі невдачі хоч однієї скасувати усі попередні.
Кожний вузол здійснює транзакцію та публікує повідомлення, яке запускає транзакції в інших вузлах.
- Вузол № 1 здійснює транзакцію та публікує повідомлення про її успішність.
- Вузол № 2 отримує повідомлення про завершення операції у вузлі № 1 та здійснює транзакцію. Після чого публікує повідомлення про її успішність.
- Якщо під час транзакції відбулась помилка, вузол № 1 скасовує транзакцію.
- Вузол № 3 отримує повідомлення про успішне завершення операції у вузлі № 2 та здійснює транзакцію. Після чого публікує повідомлення про її успішність.
- Якщо під час транзакції відбулась помилка, вузол № 2 скасовує транзакцію.
- Вузол № 1 отримує повідомлення про скасування транзакції від вузла № 2 (або про помилку виконання у вузлі № 3) і також скасовує транзакцію.
- Координатор по черзі надсилає запит на здійснення транзакції кожному вузлу.
- Якщо хоч один вузол відповідає про помилку здійснення транзакції, координатор у зворотному порядку надсилає запити про скасування транзакції усім вузлам.
- Складність реалізації компенсаційних транзакцій. Порядок кроків при скасуванні транзакції не завжди дзеркальний початковій операції. Також при скасуванні транзакції варто враховувати, що дані могли бути змінені іншими транзакціями.
Приклад реалізації мовою С#
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!"); } } }