Реализация Saga с постоянным хранилищем в .net. Альтернатива Masstransit Saga?

#.net #state-machine #masstransit #saga

#.net #конечный автомат #masstransit #сага

Вопрос:

Я использую автоматонимичный конечный автомат с MassTransit. Мне понравилось работать с этой машиной состояния / саги, особенно с тем, как она была настроена и настроена, а также с тем, что я могу передавать конечному автомату события, которые реализуют контракты, которые будут использоваться в качестве сообщений.

Вот как это может выглядеть:

  //define the statemachine with a State class (ServiceState)
 public class ServiceStateMachine :
    AutomatonymousStateMachine<ServiceState>{

    //define available states
    public State Available { get; set; }
    public State WaitForItem { get; set; }

    //define available events
    public Event<RequestItem> RequestItem { get; set; }

    //configure the state machine and configure the store to use the ServiceState class
    public void ConfigureStateMachineCorrelations(StateMachineSagaRepositoryConfigurator<ServiceState> r)

     //bind events to contracts and conditions
     r.Correlate(RequestItem,
            (state, message) =>
                state.CorrelationId == message.CorrelationId)
    }


    public ServiceStateMachine(IStateMachineActivityFactory activityFactory
    {
         State(() => Available);
         State(() => WaitForItem);

         Event(() => RequestItem);

         //bind states, events, activities, custom actions...             
         During(Available,
            When(RequestItem)
                .Then((state, message) =>
                {
                    state.ServiceId = message.ServiceId; // just an example baby!
                })
                .TransitionTo(WaitForItem)
                .Then(() => _activityFactory.GetActivity<RequestItemActivity, ServiceState>())
    }
 

Какие существуют альтернативные реализации Saga, которые похожи, но не связаны с архитектурами MQ? Я думаю, что я действительно ищу реализацию конечного автомата или Saga, по крайней мере, с постоянным хранилищем в памяти.

Комментарии:

1. Вы можете использовать Automatonymous сам по себе, полностью отдельно от MassTransit. Фактически, интеграция с NHibernate также представляет собой отдельную библиотеку.

2. @ChrisPatterson Не могли бы вы ответить на этот вопрос примером. У меня более или менее тот же вопрос, что и у OP, но мне трудно найти полный пример сохранения. Спасибо!

Ответ №1:

Вы можете использовать Automatonymous, полностью независимую от MassTransit (или любой системы обмена сообщениями). Существуют методы (и функции подъема для выделения конечного автомата или события) для создания событий, как с данными, так и без них.

 _machine.RaiseEvent(instance, x => x.RequestItem, itemData);
 

Простой конечный автомат и реализации экземпляра не имеют понятия хранилища состояния (экземпляра), которое зависит от приложения. Например, вы можете ввести словарь в память или использовать сборку NHibernate, которая упрощает сохранение экземпляра состояния в базе данных SQL (сборка в основном включает помощники для сопоставления CurrentState свойства, а также несколько других пользовательских типов.

Это пример, который был запрошен онлайн, который я недавно написал в качестве модульного теста для последней ветки (mt3) Automatonymous:

 class PhoneStateMachine :
    AutomatonymousStateMachine<PrincessModelTelephone>
{
    public PhoneStateMachine()
    {
        InstanceState(x => x.CurrentState);

        State(() => OffHook);
        State(() => Ringing);
        State(() => Connected);
        State(() => OnHold, Connected);
        State(() => PhoneDestroyed);

        Event(() => ServiceEstablished);
        Event(() => CallDialed);
        Event(() => HungUp);
        Event(() => CallConnected);
        Event(() => LeftMessage);
        Event(() => PlacedOnHold);
        Event(() => TakenOffHold);
        Event(() => PhoneHurledAgainstWall);

        Initially(
            When(ServiceEstablished)
                .Then(context => context.Instance.Number = context.Data.Digits)
                .TransitionTo(OffHook));

        During(OffHook,
            When(CallDialed)
                .TransitionTo(Ringing));

        During(Ringing,
            When(HungUp)
                .TransitionTo(OffHook),
            When(CallConnected)
                .TransitionTo(Connected));

        During(Connected,
            When(LeftMessage).TransitionTo(OffHook),
            When(HungUp).TransitionTo(OffHook),
            When(PlacedOnHold).TransitionTo(OnHold));

        During(OnHold,
            When(TakenOffHold).TransitionTo(Connected),
            When(PhoneHurledAgainstWall).TransitionTo(PhoneDestroyed));

        DuringAny(
            When(Connected.Enter)
                .Then(context => StartCallTimer(context.Instance)),
            When(Connected.Leave)
                .Then(context => StopCallTimer(context.Instance)));
    }


    public State OffHook { get; set; }
    public State Ringing { get; set; }
    public State Connected { get; set; }
    public State OnHold { get; set; }
    public State PhoneDestroyed { get; set; }

    public Event<PhoneServiceEstablished> ServiceEstablished { get; set; }
    public Event CallDialed { get; set; }
    public Event HungUp { get; set; }
    public Event CallConnected { get; set; }
    public Event LeftMessage { get; set; }
    public Event PlacedOnHold { get; set; }
    public Event TakenOffHold { get; set; }
    public Event PhoneHurledAgainstWall { get; set; }

    void StopCallTimer(PrincessModelTelephone instance)
    {
        instance.CallTimer.Stop();
    }

    void StartCallTimer(PrincessModelTelephone instance)
    {
        instance.CallTimer.Start();
    }
}
 

Он создается и вызывается (модель Princess является экземпляром состояния для примера), как показано ниже:

 var phone = new PrincessModelTelephone();
await _machine.RaiseEvent(phone, _machine.ServiceEstablished, new PhoneServiceEstablished {Digits = "555-1212"});

await _machine.RaiseEvent(phone, x => x.CallDialed);
await _machine.RaiseEvent(phone, x => x.CallConnected);
await _machine.RaiseEvent(phone, x => x.PlacedOnHold);

await Task.Delay(10);

await _machine.RaiseEvent(phone, x => x.HungUp);
 

Я уверен, что есть и другие примеры, но конечный автомат отделен от любых зависимостей, что делает его пригодным для использования в любом месте.