I have a fairly simple app in which I need some simplified version of a saga pattern. It's a monolith type of app where I need to just grab a bunch of injected services and perform some actions of those services one by one. For simplicity, let's call those services "steps" so it's pretty much like a multi step scenario where each step action is actually a method on some injected service. That's easy of course. But I'm wondering about some clever error handling with fallback methods. So let's say I have three services and three method to call on them. If first one fails, I need to fire a fallback method on it. If the second one fails, I need to call a fallback from the second and then the first one. If the third one fail, I need to call fallback from third step, then second and lastly from the first one. So something similar to saga pattern of the microservices world, just done in a non-distributed app.
Easy to code with simple try/catch blocks as in example below. I'm just looking for more clever way to do that thing. Any tips about how this piece of code could be refactored into something more manageable? It looks very crappy especially if it grows into something like ten steps scenario.
I played around with the workflow-core package (https://github.com/danielgerlag/workflow-core) as well as the Elsa workflows (https://elsa-workflows.github.io/elsa-core/) but they just seem like an overkill for that. I don't want to go into fully message or event driven model in my app. I just need that nice fallback mechanism that they allow you to do.
https://dotnetfiddle.net/WRLRy1
public class Program
{
public static void Main()
{
var step1 = new Step1();
var step2 = new Step2();
var step3 = new Step3();
try
{
step1.DoSomethingInStep1();
}
catch
{
step1.FallbackFromStep1();
}
try
{
step2.DoSomethingInStep2();
}
catch
{
step2.FallbackFromStep2();
step1.FallbackFromStep1();
}
try
{
step3.DoSomethingInStep3();
}
catch
{
step3.FallbackFromStep3();
step2.FallbackFromStep2();
step1.FallbackFromStep1();
}
}
}
public class Step1
{
public void DoSomethingInStep1() {}
public void FallbackFromStep1() {}
}
public class Step2
{
public void DoSomethingInStep2() {}
public void FallbackFromStep2() {}
}
public class Step3
{
public void DoSomethingInStep3() {}
public void FallbackFromStep3() {}
}
CodePudding user response:
You can use a stack to save all the rollback steps needed along the work progress. When it goes wrong, simply run all the rollback steps in the stack.
Here is the code -
public interface IStep
{
public void Do();
public void Rollback();
}
public static class StepHelper
{
public static void RunSteps(List<IStep> steps)
{
var rollback = new Stack<Action>();
try
{
foreach (var step in steps)
{
rollback.Push(step.Rollback);
step.Do();
}
}
catch (Exception ex)
{
//handle exception...
//roll back all changes
while (rollback.Count > 0)
{
rollback.Pop()();
}
}
}
}
public class Step1 : IStep
{
public void Do()
{
Console.WriteLine("Do step1 ...");
}
public void Rollback()
{
Console.WriteLine("Roll back step1 ...");
}
}
public class Step2 : IStep
{
public void Do()
{
Console.WriteLine("Do step2 ...");
}
public void Rollback()
{
Console.WriteLine("Roll back step2 ...");
}
}
public class Step3 : IStep
{
public void Do()
{
Console.WriteLine("Do step3 ...");
throw new Exception("test exception...");
}
public void Rollback()
{
Console.WriteLine("Roll back step3 ...");
}
}
Test run -
StepHelper.RunSteps(new List<IStep>()
{
new Step1(),
new Step2(),
new Step3()
});