StepWise is a workflow engine build with C# and typescript. In StepWise, you define a workflow in C#, then visualize and execute it in StepWise UI.
Here's a simple example of how to define a workflow to prepare dinner. The workflow consists of several steps, such as chopping vegetables, boiling water, cooking pasta, and cooking sauce. The final step is to serve dinner, which depends on all the previous steps. When executed, the workflow will automatically resolve the dependencies between steps and execute them in the parallel if possible.
using StepWise;
public class PrepareDinner
{
[Step]
public async Task<string> ChopVegetables(string[] vegetables)
{
await Task.Delay(3000);
return $"Chopped {string.Join(", ", vegetables)}";
}
[Step]
public async Task<string> BoilWater()
{
await Task.Delay(2000);
return "Boiled water";
}
[Step]
public async Task<string> CookPasta()
{
await Task.Delay(5000);
return "Cooked pasta";
}
[Step]
public async Task<string> CookSauce()
{
await Task.Delay(4000);
return "Cooked sauce";
}
[Step]
[DependOn(nameof(ChopVegetables))]
[DependOn(nameof(BoilWater))]
[DependOn(nameof(CookPasta))]
[DependOn(nameof(CookSauce))]
public async Task<string> ServeDinner(
[FromStep(nameof(ChopVegetables))] string[] vegetables,
[FromStep(nameof(BoilWater))] string water,
[FromStep(nameof(CookPasta))] string pasta,
[FromStep(nameof(CookSauce))] string sauce)
{
return $"Dinner ready!";
}
}
// Usage
var prepareDinner = new PrepareDinner();
var workflow = Workflow.CreateFromInstance(prepareDinner);
var engine = new WorkflowEngine(workflow, maxConcurrency: 10);
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var inputVariables = new Dictionary<string, object>
{
[nameof(ChopVegetables)] = StepVariable.Create(new[] { "tomato", "onion", "garlic" }),
};
await foreach (var stepResult in engine.ExecuteAsync(nameof(ServeDinner), inputVariables))
{
// print every step result
// ChopVegetables: Chopped tomato, onion, garlic
// BoilWater: Boiled water
// CookPasta: Cooked pasta
// CookSauce: Cooked sauce
// ServeDinner: Dinner ready!
Console.WriteLine(stepResult);
}
stopwatch.Stop();
// Because the steps are executed in parallel, the total time should be less than the sum of individual step times
stopwatch.ElapsedMilliseconds.Should().BeLessThan(6000);
StepWise UI is a built-in WebUI for visualizing and executing workflows. To use StepWise UI, simply add the following code to your project:
// program.cs
var host = Host.CreateDefaultBuilder()
//.UseEnvironment("Development")
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseUrls("http://localhost:5123");
})
.UseStepWiseServer()
.Build();
await host.StartAsync();
Then, use StepWiseClient
to add workflows to StepWise UI:
var stepWiseClient = host.Services.GetRequiredService<StepWiseClient>();
// Add the workflow to the StepWise server
stepWiseClient.AddWorkflow(prepareDinner);
// Wait for the host to shutdown
await host.WaitForShutdownAsync();
Now, you can visit http://localhost:5123
to see the StepWise UI and execute the workflow.
You can find more examples in the examples directory.
In StepWise, you can define dependencies between steps using the [DependsOn]
attribute. This ensures that a step is executed only after its dependencies have been satisfied.
[!Note] Prevent circular dependencies between steps, otherwise, the workflow engine will remind you with an exception.
Variable dependencies of a step means that the step requires certain variables to be available in the context before it can be executed. If all variable dependencies are met, the step can be executed in parallel with other steps that don't have dependencies on it. In StepWise, variable dependencies are the input parameters of a step.
[!Note]
[FromStep]
attribute doesn't affect the step dependency. It is used to pass the output of one step as input to another step.
StepWise automatically manages dependencies between Steps:
[DependsOn]
attribute to specify dependencies between Steps.StepWise supports parallel execution of steps that do not have step dependencies on each other. This can significantly improve the performance of your workflows by executing independent steps concurrently.
StepWiseEngine
StepWiseEngine
is the core component of StepWise that manages the execution of workflows. It uses a consumer-producer approach to execute steps in the correct order while handling dependencies between steps and parallel execution when possible. You can visit this documentation to learn more about how the StepWiseEngine
works.
StepWise is built around two main primitives:
A Step is the smallest unit of work in StepWise. It represents a single task or operation within a workflow.
[Step]
attribute.[DependsOn]
attribute.[Step]
[DependsOn(nameof(OtherStep))]
[DependsOn(nameof(AnotherStep))]
public Task<Data> GetData(int id)
{
// Implementation
}
A Workflow is a collection of Steps that together accomplish a larger task.
Usage:
public class DataProcessingWorkflow
{
[Step(Name = "GetData")]
public Task<Data> GetData(int id) { /* ... */ }
[Step(Name = "ProcessData")]
[DependsOn(nameof(GetData))]
public Task<Result> ProcessData([FromStep("GetData")] Data data) { /* ... */ }
[Step(Name = "SaveResult")]
[DependsOn(nameof(ProcessData))]
public Task<string> SaveResult([FromStep("ProcessData")] Result result) { /* ... */ }
}
We welcome contributions to StepWise! Please see our Contributing Guide for more details.
StepWise is released under the MIT License. See the LICENSE file for details.
If you encounter any issues or have questions, please file an issue on the GitHub issue tracker.