I built a code piece that makes it easy to execute a set of sequential operations, that might depend on the same parameter value (passed across the execution) or might need a particular parameter.
There's also a possibility to manage an error from within the operations, by implementing IReversible
interface.
This code example can be copy/pasted into a Console App, so you can easily check the functionality.
So far, this is what I've done:
class Program
{
static void Main(string[] args)
{
var transferenceInfo = new InterbankTranferenceInfo();
var orchestrator = new Orchestrator(new InternalDebitOperation(transferenceInfo),
new InterbankCreditOperation(),
new CommissionOperation());
orchestrator.Run();
}
}
public class InterbankTranferenceInfo : IParameter
{
public bool InternalDebitDone { get; set; }
public bool InterbankCreditDone { get; set; }
public bool CommissionDone { get; set; }
}
public class InternalDebitOperation : Operation<InterbankTranferenceInfo>, IOperation<InterbankTranferenceInfo>
{
public InternalDebitOperation(InterbankTranferenceInfo parameter)
: base(parameter)
{
}
public override InterbankTranferenceInfo Execute()
{
return new InterbankTranferenceInfo() { InternalDebitDone = true };
}
}
public class InterbankCreditOperation : Operation<InterbankTranferenceInfo>, IOperation<InterbankTranferenceInfo>
{
public override InterbankTranferenceInfo Execute()
{
Parameter.InterbankCreditDone = true;
return Parameter;
}
}
public class CommissionOperation : Operation<InterbankTranferenceInfo>, IReversible, IOperation<InterbankTranferenceInfo>
{
public override InterbankTranferenceInfo Execute()
{
Parameter.CommissionDone = true;
// Uncomment this code to test Reverse operation.
// throw new Exception("Test exception, it should trigger Reverse() method.");
return Parameter;
}
public void Reverse()
{
Parameter.CommissionDone = false;
}
}
public enum OperationStatus
{
Done,
Pending,
Reversed
}
public interface IParameter
{
}
public interface IReversible
{
void Reverse();
}
public interface IOperation<out T> where T : IParameter
{
bool GetParameterFromParentOperation { get; }
OperationStatus Status { get; set; }
IParameter Execute(IParameter parameter);
T Execute();
}
//[System.Diagnostics.DebuggerStepThroughAttribute()]
public abstract class Operation<T> : IOperation<T> where T : IParameter
{
public T Parameter { get; private set; }
public bool GetParameterFromParentOperation { get { return this.Parameter == null; } }
public OperationStatus Status { get; set; }
public Operation()
{
Status = OperationStatus.Pending;
}
public Operation(IParameter parameter)
{
Status = OperationStatus.Pending;
this.Parameter = (T)parameter;
}
public abstract T Execute();
public virtual IParameter Execute(IParameter parameter)
{
this.Parameter = (T)parameter;
return this.Execute();
}
}
public class Orchestrator
{
public List<IOperation<IParameter>> Operations { get; private set; }
public Orchestrator(params IOperation<IParameter>[] operations)
{
this.Operations = new List<IOperation<IParameter>>();
foreach (var item in operations)
{
this.Operations.Add((IOperation<IParameter>)item);
}
}
public IParameter Run()
{
IParameter previousOperationResult = null;
foreach (var operation in this.Operations)
{
try
{
if (operation.GetParameterFromParentOperation)
previousOperationResult = operation.Execute(previousOperationResult);
else
previousOperationResult = operation.Execute();
operation.Status = OperationStatus.Done;
}
catch (Exception)
{
foreach (var o in this.Operations)
{
if (o is IReversible)
{
((IReversible)o).Reverse();
o.Status = OperationStatus.Reversed;
}
else
throw;
}
break;
}
}
return previousOperationResult;
}
}
Questions
- What you guys think about it;
- Am I reinventing the wheel? Is there any pattern or fwk that covers this functionality? It looks like Command Pattern with steroids, or is it other pattern?
- Do you see any possible future headache on this, or maybe have any suggestion about how to improve it.
I don't like the implementation of both Operation and IOperation on concrete types, so feel free to correct me if there's a more elegant way of doing this covariant scenario.
EDIT
Code edited due to @Dan Lyons suggestion removed unnecessary IExecutableOperation<T>
and IInternalOperation<T>
, joined their signatures to IOperation<out T>
.
EDIT2
Per @svick great suggestions, the code now looks like this. Now I don't even remember why I was so lost with covariance issues, this didn't even needed covariance usage.
class Program
{
static void Main(string[] args)
{
var transferenceInfo = new InterbankTranferenceInfo();
var orchestrator = new Orchestrator<InterbankTranferenceInfo>(new InternalDebitOperation(transferenceInfo),
new InterbankCreditOperation(),
new CommissionOperation());
orchestrator.Run();
}
}
public class InterbankTranferenceInfo
{
public bool InternalDebitDone { get; set; }
public bool InterbankCreditDone { get; set; }
public bool CommissionDone { get; set; }
}
public class InternalDebitOperation : Operation<InterbankTranferenceInfo>, IReversible
{
public InternalDebitOperation(InterbankTranferenceInfo parameter)
: base(parameter)
{
}
protected override InterbankTranferenceInfo ExecuteInternal()
{
this.Parameter.InternalDebitDone = true;
return base.ExecuteInternal();
}
public void Reverse()
{
this.Parameter.InterbankCreditDone = false;
}
}
public class InterbankCreditOperation : Operation<InterbankTranferenceInfo>, IReversible
{
protected override InterbankTranferenceInfo ExecuteInternal()
{
this.Parameter.InterbankCreditDone = true;
return base.ExecuteInternal();
}
public void Reverse()
{
this.Parameter.InterbankCreditDone = false;
}
}
public class CommissionOperation : Operation<InterbankTranferenceInfo>, IReversible
{
protected override InterbankTranferenceInfo ExecuteInternal()
{
// Uncomment this code to test Reverse operation.
// throw new Exception("Test exception, it should trigger Reverse() method.");
this.Parameter.CommissionDone = true;
return base.ExecuteInternal();
}
public void Reverse()
{
this.Parameter.CommissionDone = false;
}
}
public interface IReversible
{
void Reverse();
}
public enum OperationStatus
{
Done,
Pending,
Reversed
}
public interface IOperation<T>
{
OperationStatus Status { get; set; }
T Parameter { get; set; }
T Execute(T parameter);
}
public abstract class Operation<T> : IOperation<T>
{
public T Parameter { get; set; }
public OperationStatus Status { get; set; }
protected Operation()
{
Status = OperationStatus.Pending;
}
protected Operation(T parameter)
{
Parameter = parameter;
}
protected virtual T ExecuteInternal()
{
return this.Parameter;
}
T IOperation<T>.Execute(T parameter)
{
Parameter = parameter;
return ExecuteInternal();
}
}
public class Orchestrator<T>
{
public IOperation<T>[] Operations { get; private set; }
public Orchestrator(params IOperation<T>[] operations)
{
Operations = operations;
}
public T Run()
{
T previousOperationResult = default(T);
foreach (var operation in this.Operations)
{
try
{
if (previousOperationResult == null)
previousOperationResult = operation.Execute(operation.Parameter);
else
previousOperationResult = operation.Execute(previousOperationResult);
operation.Status = OperationStatus.Done;
}
catch (Exception ex)
{
foreach (var o in this.Operations)
{
if (o is IReversible)
{
if (o.Status == OperationStatus.Done)
{
((IReversible)o).Reverse();
o.Status = OperationStatus.Reversed;
}
}
}
throw new ReversedException(string.Format("There's been a problem on operation {0}. All the reversable operations has been reversed.", operation.GetType().Name), ex);
}
}
return previousOperationResult;
}
}
public class ReversedException : Exception
{
public ReversedException(string message, Exception ex)
: base(message, ex)
{
}
}