Gal Segal's Blog

Thoughts of a programmer with a soul

Jan 31

Simple Process Manager

Tags:

Simple Process Manager

It is a common matter to have an operation in the server that requires few steps. Sometimes it is necessary that all the steps will complete successfully, and if one fails, a rollback should be made. SQL has the transaction term to support this kind of situation, and I’m about to show how it can be done in code.

My role of thumb for this kind of infrastructure is that the code should be easy to use, flexible enough for many different cases, and relays on a default behavior that can be overridden when needed.

Now lets think about a process of this kind:

The Single Step

  1. A process is created from small steps, each one is responsible for a single task.
  2. Each step should get the necessary input data and manipulate it.
  3. Each step should know how to execute its task. (Daa..)
  4. Each task should have the knowledge  how to rollback this task.
  5. Each step should be able to make several retry attempts for the execution and rollback.
  6. Each step should report if the action was successful or failed.
The base class for the step has the default behavior, which means:
  • Execute – run the task
  • Retry – do “Execute” again
  • Rollback – do nothing
  • Retry Rollback  - do Rollback again
I used an enum for reporting the state of the action:
public enum eStatus
{
 Pending,
 Success,
 Error
}
It looks like this:
public abstract class BaseStep
{
 public virtual int ExecuteAttempts { get { return 1; } }
 public virtual int RollbackAttempts { get { return 1; } }

 public abstract eStatus Execute();
 public virtual eStatus Retry()
 {
  return Execute();
 }
 public virtual eStatus Rollback()
 {
  return eStatus.Success;
 }
 public virtual eStatus RetryRollback()
 {
  return Rollback();
 }
}

The Process Manager

The manager is responsible for creating the steps in the right order and execute each one. It is also responsible for coordinating the retry attempts and rollbacks. It implements an interface with one method:

public interface IProcessManager
{
 eStatus Process();
}

and a base class that encapsulates the default behavior:

public abstract class BaseProcessManager : IProcessManager
{
	private eStatus _currentStatus;
	private int _currentStep;
	private Func<eStatus> _action;
	protected List<BaseStep> _steps;

	protected BaseProcessManager()
	{
		_currentStatus = eStatus.Pending;
		_steps = new List<BaseStep>();
		Setup();
	}

	protected abstract void Setup();

	public virtual eStatus Process()
	{
		do
		{
			_action = _steps[_currentStep].Execute;
			Run();
			_action = _steps[_currentStep].Retry;
			Retry(_steps[_currentStep].ExecuteAttempts);

			_currentStep++;

		} while (_currentStatus != eStatus.Error && _currentStep < _steps.Count);

		if (_currentStatus == eStatus.Success)
			return eStatus.Success;

		do
		{
			_currentStep--;

			_action = _steps[_currentStep].Rollback;
			Run();
			_action = _steps[_currentStep].RetryRollback;
			Retry(_steps[_currentStep].RollbackAttempts);

		} while (_currentStep > 0);

		return eStatus.Error;
	}

	private void Run()
	{
		_currentStatus = eStatus.Pending;
		_currentStatus = _action.Invoke();
	}

	private void Retry(int numberOfAttempts)
	{
		int attempt = 1;
		while (_currentStatus == eStatus.Error && attempt < numberOfAttempts)
		{
			_currentStatus = _action.Invoke();
			attempt++;
		}
	}
}

Now what is left for a developer to do is:

  1. Implement The BaseProcessManager, and override the Setup() method, where he should create the steps.
  2. Create steps that implement the BaseStep abstract class, with the step logic.

Thats it!I added a small app demonstrating this ides – grab it here.Special thanks to Lior Hakim and Omry Hay!

Back to top