1

I am trying to make a state machine that should send an email if my action ends with an OK status OR it should repeat the action at least n-times until it ends with an OK status. If the n-times threshold is exceeded, the state machine should just do a rollback and stop.

I played around with the states and some of the method provided, but I couldn't manage to use the TransitionTo(State) method inside an if to act like a 'goto' and jump to the State ignoring every line of code below this point.

So, to put it in C# pseudocode, it looks like this (this is just a representation in case the state machine code is hard to understand, I do not use this code in my app) :

  numberOfRetries = 5;

CheckActionStatus:
  if (GetActionStatus() == Status.OK)
  {
    return SendEmailToUser();
  }
  else
  {
    numberOfRetries--;
  }

  if (numberOfRetries > 0)
  {
    goto CheckActionStatus;
  }
  else
  {
    return RollbackAction();
  }

And the state machine code that I use (keep in mind that the decrement of the numberOfRetries is done within GetActionStatus())

WhenEnter(Check, binder => binder
  .Then(x => x.Instance.ActionStatus = GetActionStatus())
  .If(x => x.Instance.ActionStatus == Status.OK,
    x => x.TransitionTo(SendEmailToUser))
  .IfElse(x => x.Instance.NumberOfRetries > 0,
    x => x.TransitionTo(Check),
    x => x.TransitionTo(Rollback)));

WhenEnter(SendEmailToUser, binder => binder
  .Then(x => Console.WriteLine("Email sent to user, everything ok!"))
  .Finalize());
WhenEnter(Rollback, binder => binder
  .Then(x => Console.WriteLine("Rollbacked!"))
  .Finalize());
            
SetCompletedWhenFinalized();

But when I run the state machine, the output I get is:

Email sent to user, everything ok!
Rollbacked!

When the expected output should be just

Email sent to user, everything ok!

or just

Rollbacked!

Because I expect that if the first if has a true condition, it should transfer to the next state (SendEmailToUser in my case) and continue execution from there. Only if the condition evaluates to false, only then it should continue to the second if (the one that checks for the retries).

Am I missing something?

Daniel
  • 91
  • 1
  • 14

2 Answers2

2

Transitioning to another state does not transfer activity execution, it would continue with the next IfElse activity. So you'd need both to be IfElse activities.

Chris Patterson
  • 28,659
  • 3
  • 47
  • 59
  • Thanks for making it clear! I transformed the first *If* in an *IfElse*, but the behaviour remains the same, it still goes into two states. Can I do an IfElse Activity inside another IfElse Activity? – Daniel Jul 09 '21 at 08:05
  • Still, can you explain why Finalize() doesn't stop the state machine? If, let's say, I place another TransitionTo() after a Finalize(), the state machine will transition to that state, but in my perception, the state machine should stop when the Finalize() is called. – Daniel Jul 09 '21 at 08:39
  • 1
    Good news, I managed to achieve what I wanted by placing an IfElse on the Else caluse, I'll generate an answer. Thanks for the help! – Daniel Jul 09 '21 at 08:40
  • Finalize just transitions the state to final, it doesn't prevent subsequent activities from executing during the current event behavior. Then, once completed, the instance is removed from the saga repository (with SetCompletedWhenFinalized). – Chris Patterson Jul 09 '21 at 11:59
1

After Chris's answer, I changed the way I implemented my state machine in two different ways: Method 1: nested if-else

WhenEnter(Check, binder => binder
  .Then(x => x.Instance.ActionStatus = GetActionStatus())
  .IfElse(x => x.Instance.ActionStatus == Status.OK,
    x => x.TransitionTo(SendEmailToUser),
    xx => xx.IfElse(xy => xy.Instance.NumberOfRetries > 0,
      x => x.TransitionTo(Check),
      x => x.TransitionTo(Rollback))));

WhenEnter(SendEmailToUser, binder => binder
  .Then(x => Console.WriteLine("Email sent to user, everything ok!"))
  .Finalize());
WhenEnter(Rollback, binder => binder
  .Then(x => Console.WriteLine("Rollbacked!"))
  .Finalize());
            
SetCompletedWhenFinalized();

This way, the state finishes with the if-else and nothing more is executed, making sure the transition to desired state is done.

Method 2: intermediate state

WhenEnter(Check, binder => binder
  .Then(x => x.Instance.ActionStatus = GetActionStatus())
  .IfElse(x => x.Instance.ActionStatus == Status.OK,
    x => x.TransitionTo(SendEmailToUser),
    x => x.TransitionTo(RetryCheck)));

WhenEnter(RetryCheck, binder => binder
  .IfElse(x => x.Instance.NumberOfRetries > 0,
    x => x.TransitionTo(Check),
    x => x.TransitionTo(Rollback)));

WhenEnter(SendEmailToUser, binder => binder
  .Then(x => Console.WriteLine("Email sent to user, everything ok!"))
  .Finalize());
WhenEnter(Rollback, binder => binder
  .Then(x => Console.WriteLine("Rollbacked!"))
  .Finalize());
            
SetCompletedWhenFinalized();

This method also works and I find it more easy to understand since you basically just add another condition to your state machine.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Daniel
  • 91
  • 1
  • 14