Add StellaOps.Workflow engine: 14 libraries, WebService, 8 test projects
Extract product-agnostic workflow engine from Ablera.Serdica.Workflow into standalone StellaOps.Workflow.* libraries targeting net10.0. Libraries (14): - Contracts, Abstractions (compiler, decompiler, expression runtime) - Engine (execution, signaling, scheduling, projections, hosted services) - ElkSharp (generic graph layout algorithm) - Renderer.ElkSharp, Renderer.ElkJs, Renderer.Msagl, Renderer.Svg - Signaling.Redis, Signaling.OracleAq - DataStore.MongoDB, DataStore.PostgreSQL, DataStore.Oracle WebService: ASP.NET Core Minimal API with 22 endpoints Tests (8 projects, 109 tests pass): - Engine.Tests (105 pass), WebService.Tests (4 E2E pass) - Renderer.Tests, DataStore.MongoDB/Oracle/PostgreSQL.Tests - Signaling.Redis.Tests, IntegrationTests.Shared Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
30
docs/workflow/tutorials/01-hello-world/README.md
Normal file
30
docs/workflow/tutorials/01-hello-world/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Tutorial 1: Hello World
|
||||
|
||||
The simplest possible workflow: initialize state from a start request, activate a single human task, and complete the workflow when the task is done.
|
||||
|
||||
## Concepts Introduced
|
||||
|
||||
- `IDeclarativeWorkflow<T>` — the contract every workflow implements
|
||||
- `WorkflowSpec.For<T>()` — the builder entry point
|
||||
- `.InitializeState()` — transforms the start request into workflow state
|
||||
- `.StartWith(task)` — sets the first task to activate
|
||||
- `WorkflowHumanTask.For<T>()` — defines a human task
|
||||
- `.OnComplete(flow => flow.Complete())` — terminal step
|
||||
|
||||
## What Happens at Runtime
|
||||
|
||||
1. Client calls `StartWorkflowAsync` with `WorkflowName = "Greeting"` and payload `{ "customerName": "John" }`
|
||||
2. State initializes to `{ "customerName": "John" }`
|
||||
3. Task "Greet Customer" is created with status "Pending"
|
||||
4. A user assigns the task to themselves, then completes it
|
||||
5. `OnComplete` executes `.Complete()` — the workflow finishes
|
||||
|
||||
## Variants
|
||||
|
||||
- [C# Fluent DSL](csharp/)
|
||||
- [Canonical JSON](json/)
|
||||
|
||||
## Next
|
||||
|
||||
[Tutorial 2: Service Tasks](../02-service-tasks/) — call external services before or after human tasks.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
using WorkflowEngine.Abstractions;
|
||||
using WorkflowEngine.Contracts;
|
||||
|
||||
namespace WorkflowEngine.Tutorials;
|
||||
|
||||
// Start request — defines the input contract for the workflow.
|
||||
public sealed class GreetingRequest
|
||||
{
|
||||
public string CustomerName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
// Workflow definition — implements IDeclarativeWorkflow<TStartRequest>.
|
||||
public sealed class GreetingWorkflow : IDeclarativeWorkflow<GreetingRequest>
|
||||
{
|
||||
// Identity: name + version uniquely identify the workflow definition.
|
||||
public string WorkflowName => "Greeting";
|
||||
public string WorkflowVersion => "1.0.0";
|
||||
public string DisplayName => "Customer Greeting";
|
||||
|
||||
// Roles: which user roles can see and interact with this workflow's tasks.
|
||||
public IReadOnlyCollection<string> WorkflowRoles => ["DBA", "UR_AGENT"];
|
||||
|
||||
// Spec: the workflow specification built via the fluent DSL.
|
||||
public WorkflowSpec<GreetingRequest> Spec { get; } = WorkflowSpec
|
||||
.For<GreetingRequest>()
|
||||
|
||||
// InitializeState: transform the start request into the workflow's mutable state.
|
||||
// State is a Dictionary<string, JsonElement> — all values are JSON-serialized.
|
||||
.InitializeState(request => new Dictionary<string, JsonElement>
|
||||
{
|
||||
["customerName"] = JsonSerializer.SerializeToElement(request.CustomerName),
|
||||
})
|
||||
|
||||
// StartWith: register and activate this task as the first step.
|
||||
.StartWith(greetTask)
|
||||
.Build();
|
||||
|
||||
// Tasks: expose task descriptors for the registration catalog.
|
||||
public IReadOnlyCollection<WorkflowTaskDescriptor> Tasks => Spec.TaskDescriptors;
|
||||
|
||||
// Task definition: defines name, type (UI component), route (navigation), and behavior.
|
||||
private static readonly WorkflowHumanTaskDefinition<GreetingRequest> greetTask =
|
||||
WorkflowHumanTask.For<GreetingRequest>(
|
||||
taskName: "Greet Customer", // unique name within this workflow
|
||||
taskType: "GreetCustomerTask", // UI component identifier
|
||||
route: "customers/greet") // navigation route
|
||||
.WithPayload(context => new Dictionary<string, JsonElement>
|
||||
{
|
||||
// Pass state values to the task's UI payload.
|
||||
["customerName"] = context.StateValues
|
||||
.GetRequired<string>("customerName").AsJsonElement(),
|
||||
})
|
||||
// OnComplete: what happens after the user completes this task.
|
||||
.OnComplete(flow => flow.Complete()); // simply end the workflow
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"schemaVersion": "serdica.workflow.definition/v1",
|
||||
"workflowName": "Greeting",
|
||||
"workflowVersion": "1.0.0",
|
||||
"displayName": "Customer Greeting",
|
||||
|
||||
"startRequest": {
|
||||
"contractName": "GreetingRequest",
|
||||
"allowAdditionalProperties": true
|
||||
},
|
||||
|
||||
"workflowRoles": ["DBA", "UR_AGENT"],
|
||||
|
||||
"start": {
|
||||
"initializeStateExpression": {
|
||||
"$type": "object",
|
||||
"properties": [
|
||||
{
|
||||
"name": "customerName",
|
||||
"expression": { "$type": "path", "path": "start.customerName" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"sequence": {
|
||||
"steps": [
|
||||
{
|
||||
"$type": "activate-task",
|
||||
"taskName": "Greet Customer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"tasks": [
|
||||
{
|
||||
"taskName": "Greet Customer",
|
||||
"taskType": "GreetCustomerTask",
|
||||
"routeExpression": { "$type": "string", "value": "customers/greet" },
|
||||
"taskRoles": [],
|
||||
"payloadExpression": {
|
||||
"$type": "object",
|
||||
"properties": [
|
||||
{
|
||||
"name": "customerName",
|
||||
"expression": { "$type": "path", "path": "state.customerName" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"onCompleteSequence": {
|
||||
"steps": [
|
||||
{ "$type": "complete" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user