MicroFlow
Lightweight workflow engine
Install / Use
/learn @akarpov89/MicroFlowREADME
MicroFlow
Getting Started
- What is MicroFlow?
- NuGet Package
- Activities
- Nodes
- Data flow
- Flow creation
- Fault handling
- Graph generator
- Sample
- License
What is MicroFlow?
MicroFlow is a lightweight workflow engine. It allows to build workflows as flowcharts. Every flow is constructed from a limited number of connected nodes.
Features:
- Code-centric, no XAML
- Data flow friendly: easy to pass data from one activity to another
- Integrated dependency injection
- Flow validation
- Visualization support
Available node types:
- activity node represents user-defined action;
- condition node represents decision (like
if-elsestatement); - switch node represents multiway branch (like
switchstatement); - fork-join node represents concurrent activities;
- block node groups several nodes (like blocks in programming languages).
NuGet Package
MicroFlow is available on NuGet and MyGet.
Target frameworks:
- .NET 4.0+
- .NET Core
- Portable Libraries (net45+win8+wpa81+wp8)
Activities
The user-defined activities should inherit from one of the following base classes
Activity<TResult>
The most generic activity returning the result of type TResult.
An implementation must override the method
public abstract Task<Result> Execute();
Activity
The most generic activity without returning value. An implementation must override the method
protected abstract Task ExecuteCore();
SyncActivity<TResult>
The base class of the synchronous activities returning the value of type TResult.
An implemenation must override the method:
protected abstract TResult ExecuteActivity();
SyncActivity
The base class of the synchronous activities without returning value. An implemenation must override the method:
protected abstract void ExecuteActivity();
BackgroundActivity<TResult>
Provides the way to execute a function as a separate background task. An implemenation must override the method:
protected abstract TResult ExecuteCore();
BackgroundActivity<TResult> exposes the following properties:
CancellationToken- allows the work to be cancelled;Scheduler- schedules the worker task;IsLongRunning- allows to hintTaskSchedulerthat task will be a long-running operation.
BackgroundActivity
Provides the way to execute a function as a separate background task. An implemenation must override the method:
protected abstract void ExecuteCore();
BackgroundActivity exposes the same properties as BackgroundActivity<TResult>.
IFaultHandlerActivity
The interface of all fault handlers. Every fault handler must provide the following property:
Exception Exception { get; set; }
Nodes
The FlowBuilder class provides the way to create nodes of the flow.
ActivityNode<TActivity>
var node = builder.Activity<SomeActivity>("Node name");
node.ConnectTo(anotherNode)
.ConnectFaultTo(faultHandler)
.ConnnectCancellationTo(cancellationHandler);
ConditionNode
var node = builder.Condition("Node name");
node.WithCondition(() => boolExpr);
node.ConnectFalseTo(falseBranchNode)
.ConnectTrueTo(trueBranchNode);
There is also an alternative syntax that allows to create if-then-else constructs:
var node = builder
.If("Condition1", () => boolExpr1).Then(node1)
.ElseIf("Condition2", () => boolExpr2).Then(node2)
.Else(node3);
Notice that in this case node is initial ConditionNode (the one with condition description "Condition description").
SwitchNode
var node = builder.SwitchOf<int>("Node name");
node.WithChoice(() => someIntExpression);
node.ConnectCase(0).To(caseHandler1)
.ConnectCase(1).To(caseHandler2)
.ConnectCase(42).To(caseHandler3)
.ConnectDefault(caseHandler4).
ForkJoinNode
var node = builder.ForkJoin("Node name");
var fork1 = node.Fork<SomeForkActivity>("Fork 1 name");
var fork2 = node.Fork<SomeAnotherForkActivity>("Fork 2 name");
var fork3 = node.Fork<SomeActivity>("Fork 3 name");
BlockNode
var node = builder.Block("Optional node name", (block, blockBuilder) =>
{
var activity1 = blockBuilder.Activity<SomeActivity>();
var activity2 = blockBuilder.Activity<SomeAnotherActivity>();
activity1.ConnectTo(activity2);
});
Default fault handler
Every activity node should be connected with some specific or default fault handler
builder.WithDefaultFaultHandler<MyFaultHandler>();
Default cancellation handler
Every activity node should be connected with some specific or default cancellation handler
builder.WithDefaultCancellationHandler<MyCancellationHandler>();
Data flow
As flow executes data transfers from one activity to another. The MicroFlow has two mechanisms to define the data flow: bindings and variables.
In the examples below we will use the following activities:
public class ReadIntActivity : SyncActivity<int>
{
protected override int ExecuteActivity()
{
return int.Parse(Console.ReadLine());
}
}
public class SumActivity : SyncActivity<int>
{
[Required] public int FirstNumber { get; set; }
[Required] public int SecondNumber { get; set; }
protected override int ExecuteActivity()
{
return FirstNumber + SecondNumber;
}
}
Binding to activity result
In this example we bind properties FirstNumber and SecondNumber to the results
of readFirstNumber and readSecondNumber:
var readFirstNumber = builder.Activity<ReadIntActivity>();
var readSecondNumber = builder.Activity<ReadIntActivity>();
var sumTwoNumbers = builder.Activity<SumActivity>();
sumTwoNumbers.Bind(a => a.FirstNumber).ToResultOf(readFirstNumber);
sumTwoNumbers.Bind(a => a.SecondNumber).ToResultOf(readSecondNumber);
Binding to value
In this example we bind FirstNumber to the value 42 and SecondNumber to 5:
var sumTwoNumbers = builder.Activity<SumActivity>();
sumTwoNumbers.Bind(a => a.FirstNumber).To(42);
sumTwoNumbers.Bind(a => a.SecondNumber).To(5);
Binding to expression
In this example we bind FirstNumber to expression using the result of the readFirstNumber
and SecondNumber to function call Factorial(5) expression:
var readFirstNumber = builder.Activity<ReadIntActivity>();
var firstNumber = Result<int>.Of(readFirstNumber); // Create result thunk
var sumTwoNumbers = builder.Activity<SumActivity>();
sumTwoNumbers.Bind(a => a.FirstNumber).To(() => firstNumber.Get() + 1);
sumTwoNumbers.Bind(a => a.SecondNumber).To(() => Factorial(5));
Using flow variables
The MicroFlow allows to create and use variables scoped to the whole flow or to some specific block:
var globalVariable = builder.Variable<int>();
var block = builder.Block("my block", (thisBlock, blockBuilder) =>
{
var localVariable = thisBlock.Variable<string>("initial value");
});
It's possible to change the variable value after completion of some activity:
Assign activity result:
var myVar = builder.Variable<int>();
var readFirstNumber = builder.Activity<ReadIntActivity>();
myVar.BindToResultOf(readFirstNumber);
Assign some constant value:
var myVar = builder.Variable<bool>();
var readFirstNumber = builder.Activity<ReadIntActivity>();
readFirstNumber.OnCompletionAssign(myVar, true);
Update value without using activity result:
var myVar = builder.Variable<int>(42);
var readFirstNumber = builder.Activity<ReadIntActivity>();
readFirstNumber.OnCompletionUpdate(
myVar,
oldValue => oldValue + 1
);
Update value using activity result:
var myVar = builder.Variable<int>(42);
var readFirstNumber = builder.Activity<ReadIntActivity>();
readFirstNumber.OnCompletionUpdate(
myVar,
(int oldValue, int result) => oldValue + result
);
Later the current value of a variable can be retrieved via property CurrentValue:
var myVar = builder.Variable<int>();
...
var sumTwoNumbers = builder.Activity<SumActivity>();
sumTwoNumbers.Bind(a => a.FirstNumber)
.To(() => myVar.CurrentValue);
...
Flow creation
Every flow is a subclass of the Flow abstract class.
The Flow base class provides the way to validate and run the
the constructed flow:
public ValidationResult Validate();
public Task Run();
In order to create new flow definition it's required to describe the flow structure and give the flow a name:
public class MyFlow : Flow
{
public override string Name => "My brand new flow";
protected override void Build(FlowBuilder builder)
{
// Create and connect nodes
}
}
The Flow сlass also has several configuration extension points:
ConfigureServicesmethod allows to register services required for activities (dependency injection mechanism);ConfigureValidationmethod allows to add custom flow validators;CreateFlowExecutionLoggermethod allows to setup execution logging.
Services registration
Configuring services is possible via overriding the method ConfigureServices
protected virtual void ConfigureServices(
[NotNull]IServiceCollection services
);
Let's say our ReadIntActivity uses IReader service:
public int
Related Skills
node-connect
344.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
99.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
