A "Mini" workflow engine, part 4.1: from delegates to patterns
June 24, 2013 14:55Today I will write just a short post, to wrap-up my considerations about how to represent bookmarks.
We have seen that the key to represent episodic execution of reactive programs is to save "continuations", marking points in the program text where to suspend (and resume) execution, together with program state.
We have seen two ways of represent bookmarks in C#: using delegates/Func<> objects, and using iterators.
Before going further, I would like to introduce a third way, a variation of the "Func<> object" solution.
Instead of persisting delegates (which I find kind of interesting and clever, but limiting) we turn to an old,plain pattern solution.
A pattern which recently gained a lot of attention is CQRS (Command Query Responsibility Segregation). It is a pattern that helps to simplify the design of distributed systems, and helps scaling out. It is particularly helpful in cloud environments (my team at MSR in Trento used it for our cloud project).
Having recently used it, I remembered how CQRS (like many other architectural level patterns) is built upon other patterns; in particular, I started thinking about the Command pattern (the "first half" of CQRS).
It looks like the Command pattern could fit quite well with our model; for example, we can turn the ReadLine activity:
The execution queue will then call the Execute method, instead of invoking the delegate, and will inspect the activity status accordingly (not shown here).
Why using a pattern, instead of making both Execute and ContinueAt abstract and schedule the Activity itself using the execution queue? To maintain the same flexibility; in this way, we can have ContinueAt1 and ContinueAt2, wrapped by different Command objects, and each of them could be scheduled based on the execution of Execute.
Using objects will help during serialization: while Func<> objects and delegates are tightly coupled with the framework internals and therefore, as we have seen, require BinarySerialization, plain objects may be serialized using methods, more flexible and/or with higher performances (like XmlSerialization or protobuf-net).
Yes, it is less immediate, more cumbersome, less elegant. But it is also a more flexible, more "portable" solution.
Next time: are we done with execution? Not quite.. we are still missing repeated execution of activities! (Yes, loops and gotos!)
We have seen that the key to represent episodic execution of reactive programs is to save "continuations", marking points in the program text where to suspend (and resume) execution, together with program state.
We have seen two ways of represent bookmarks in C#: using delegates/Func<> objects, and using iterators.
Before going further, I would like to introduce a third way, a variation of the "Func<> object" solution.
Instead of persisting delegates (which I find kind of interesting and clever, but limiting) we turn to an old,plain pattern solution.
A pattern which recently gained a lot of attention is CQRS (Command Query Responsibility Segregation). It is a pattern that helps to simplify the design of distributed systems, and helps scaling out. It is particularly helpful in cloud environments (my team at MSR in Trento used it for our cloud project).
Having recently used it, I remembered how CQRS (like many other architectural level patterns) is built upon other patterns; in particular, I started thinking about the Command pattern (the "first half" of CQRS).
It looks like the Command pattern could fit quite well with our model; for example, we can turn the ReadLine activity:
[Serializable]
public class ReadLine : Activity
{
public OutArgument<string> Text = new OutArgument<string>();
protected override ActivityExecutionStatus Execute(WorkflowContext context)
{
context.CreateBookmark(this.Name, this.ContinueAt);
return ActivityExecutionStatus.Executing;
}
void ContinueAt(WorkflowContext context, object value)
{
this.Text.Value = (string)value;
CloseActivity(context);
}
}
Into the following Command:/* The Receiver class */ public class ReadLine : Activity { public OutArgument<string> Text = new OutArgument<string>(); protected override ActivityExecutionStatus Execute(WorkflowContext context) { context.CreateBookmark(this.Name, new CompleteReadLine(this)); return ActivityExecutionStatus.Executing; } void ContinueAt(WorkflowContext context, object value) { this.Text.Value = (string)value; CloseActivity(context); } } [Serializable] public interface ICommand { void Execute(WorkflowContext context);The idea is to substitute the "ContinueAt" delegates with Command objects, which will be created and enqueued to represent continuations.
} /* The Command for starting reading a line - ConcreteCommand #1 */
[Serializable]
public StartReadLine: ICommand { private readonly ReadLine readLine; public StartReadLine(ReadLine readLine) { this.readLine = readLine; } public void Execute(WorkflowContext context) { readLine.Status = readLine.Execute(context); } } /* The Command for finishing reading a line - ConcreteCommand #2 */
[Serializable]
public class CompleteReadLine : ICommand { private readonly ReadLine readLine; public CompleteReadLine(ReadLine readLine) { this.readLine = readLine; } public Object Payload { get; set; } public void Execute(WorkflowContext context) { readLine.ContinueAt(context, payload);
readLine.Status = ActivityExecutionStatus.Closed;
} }
The execution queue will then call the Execute method, instead of invoking the delegate, and will inspect the activity status accordingly (not shown here).
Why using a pattern, instead of making both Execute and ContinueAt abstract and schedule the Activity itself using the execution queue? To maintain the same flexibility; in this way, we can have ContinueAt1 and ContinueAt2, wrapped by different Command objects, and each of them could be scheduled based on the execution of Execute.
Using objects will help during serialization: while Func<> objects and delegates are tightly coupled with the framework internals and therefore, as we have seen, require BinarySerialization, plain objects may be serialized using methods, more flexible and/or with higher performances (like XmlSerialization or protobuf-net).
Yes, it is less immediate, more cumbersome, less elegant. But it is also a more flexible, more "portable" solution.
Next time: are we done with execution? Not quite.. we are still missing repeated execution of activities! (Yes, loops and gotos!)