Search code examples
design-patternsrollbacksequential-workflow

Design pattern to execute multiple sequential functions as a unit of work and support rollbacks


I have a complicated review application submission process that does several steps.

ReviewService.CreateReview()

  • CheckReservedTimeslotIsAvailable
  • ProcessPayment
  • CreateMeeting
  • InsertReview
  • UpdateFinancialJournal
  • FinalizeDocuments
  • Notify

All these steps are coded inside the CreateReview() method and is becoming un-readable, hard to manage. Also the current implementation doesn't have the support of rollbacks.

So the idea is to create an Orchestrator class and build the sequence of steps. The orchestrator ensures a successful review is created if all steps are completed. If any of the step fails to complete then all of the completed preceding functions are rolled back to ensure data integrity. This is pretty much the same as Saga pattern (Orchestrated) but with slight change the steps are not micro services.

Is this the right pattern to use? Or Command pattern would be a good option? Please advise.

BaseOrchestrator ... using System; using System.Collections.Generic;

/// <summary>
/// Facilitates runnning of the pipeline operations.
/// </summary>
/// <typeparam name="T">The type of object orchestrator is executed on.</typeparam>
public abstract class BaseOrchestrator<T> : IOrchestrator<T>
{
    protected T data;

    protected List<Action> Steps;

    protected int rejectReason;

    public BaseOrchestrator<T> Begin(Action stepToExecute)
    {
        RegisterStepToExecute(stepToExecute);
        return this;
    }

    public BaseOrchestrator<T> Step(Action stepToExecute)
    {
        RegisterStepToExecute(stepToExecute);
        return this;
    }

    public BaseOrchestrator<T> End(Action stepToExecute)
    {
        RegisterStepToExecute(stepToExecute);
        return this;
    }

    public BaseOrchestrator<T> WithRollback(Action stepToExecute)
    {
        RegisterStepToExecute(stepToExecute);
        return this;
    }

    protected BaseOrchestrator<T> RegisterStepToExecute(Action stepToExecute)
    {
        Steps.Add(stepToExecute);
        return this;
    }

    public BaseOrchestrator<T> StepBuilder()
    {
        Steps = new List<Action>();
        return this;
    }

    /// <inheritdoc/>
    public void Execute(T data)
    {
        ...
    }
}

...


Solution

  • The Command pattern is better in this situation since you're building the query as you go. You don't have to deal with data in multiple databases and consequently 2PC. Whereas if you go with Saga, you are committing transactions on each method which doesn't really benefit you much and might add more complexity in the long run.

    Suppose transient network issues arise and one method fails and rolling back works on the first two rollbacks, but then fails on the next?