Reputation: 73
I have been using Automatonymous State Machine with MassTransit. I enjoyed working with that state/saga machine, especially how it was configured and setup, as well as that I can feed the state machine with events that implements contracts to be used as messages.
This is how it can look like:
//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>())
}
What alternative Saga implementations are there that are similar, but not connected to MQ architectures? I guess what I am really looking for is a State Machine or Saga Implementation with at least an in memory persistent store.
Upvotes: 1
Views: 3567
Reputation: 33288
You can use Automatonymous completely independent of MassTransit (or any messaging system). There are methods (and lift functions to factor out the state machine or the event) for raising events, either with or without data.
_machine.RaiseEvent(instance, x => x.RequestItem, itemData);
The plain state machine and instance implementations do not have a notion of state (instance) storage, that is up to the application. For example, you could key a dictionary in memory, or you could use the NHibernate assembly that makes it easy to persist a state instance to a SQL database (the assembly mainly includes helpers for mapping the CurrentState
property, as well as a few other custom types.
This is an example that was requested online, that I recently wrote as a unit test for the latest branch (mt3) of 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();
}
}
It's created and called (the Princess model is the state instance for the example) as shown below:
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);
I'm sure there are more examples, but the state machine is separate from any dependencies making it usable anywhere.
Upvotes: 3