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:
22
docs/workflow/tutorials/05-sub-workflows/README.md
Normal file
22
docs/workflow/tutorials/05-sub-workflows/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Tutorial 5: Sub-Workflows & Continuations
|
||||
|
||||
Compose workflows by invoking child workflows — either inline (SubWorkflow) or fire-and-forget (ContinueWith).
|
||||
|
||||
## SubWorkflow vs ContinueWith
|
||||
|
||||
| Feature | `.SubWorkflow()` | `.ContinueWith()` |
|
||||
|---------|-----------------|-------------------|
|
||||
| Parent waits | Yes — resumes after child completes | No — parent completes immediately |
|
||||
| State flows back | Yes — child state merges into parent | No — child is independent |
|
||||
| Same instance | Yes — tasks appear under parent instance | No — new workflow instance |
|
||||
| Use when | Steps must complete before parent continues | Fire-and-forget, scheduled work |
|
||||
|
||||
## Variants
|
||||
|
||||
- [C# Fluent DSL](csharp/)
|
||||
- [Canonical JSON](json/)
|
||||
|
||||
## Next
|
||||
|
||||
[Tutorial 6: Advanced Patterns](../06-advanced-patterns/) — Fork, Repeat, Timer, External Signal.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using WorkflowEngine.Abstractions;
|
||||
using WorkflowEngine.Contracts;
|
||||
|
||||
namespace WorkflowEngine.Tutorials;
|
||||
|
||||
public sealed class ParentWorkflow : IDeclarativeWorkflow<PolicyChangeWorkflowRequest>
|
||||
{
|
||||
public string WorkflowName => "ParentWorkflow";
|
||||
public string WorkflowVersion => "1.0.0";
|
||||
public string DisplayName => "Parent Workflow Example";
|
||||
public IReadOnlyCollection<string> WorkflowRoles => [];
|
||||
|
||||
public WorkflowSpec<PolicyChangeWorkflowRequest> Spec { get; } = WorkflowSpec
|
||||
.For<PolicyChangeWorkflowRequest>()
|
||||
.InitializeState(WorkflowExpr.Object(
|
||||
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("start.policyId"))))
|
||||
.StartWith(BuildFlow)
|
||||
.Build();
|
||||
|
||||
public IReadOnlyCollection<WorkflowTaskDescriptor> Tasks => Spec.TaskDescriptors;
|
||||
|
||||
private static void BuildFlow(WorkflowFlowBuilder<PolicyChangeWorkflowRequest> flow)
|
||||
{
|
||||
flow
|
||||
.Call("Open For Change",
|
||||
Address.LegacyRabbit("pas_annexprocessing_alterpolicy"),
|
||||
WorkflowExpr.Object(
|
||||
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId"))),
|
||||
WorkflowHandledBranchAction.Complete,
|
||||
WorkflowHandledBranchAction.Complete)
|
||||
|
||||
// --- SubWorkflow: inline execution, parent waits ---
|
||||
// The child workflow runs within this execution.
|
||||
// Its tasks appear under the parent instance.
|
||||
// State from the child merges back into the parent after completion.
|
||||
.SubWorkflow(
|
||||
"Review Policy Changes",
|
||||
new WorkflowWorkflowInvocationDeclaration
|
||||
{
|
||||
WorkflowName = "ReviewPolicyOpenForChange",
|
||||
PayloadExpression = WorkflowExpr.Object(
|
||||
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId")),
|
||||
WorkflowExpr.Prop("productCode", WorkflowExpr.Path("state.productCode"))),
|
||||
})
|
||||
// Execution resumes here after child completes.
|
||||
|
||||
// --- ContinueWith: fire-and-forget ---
|
||||
// The parent workflow completes immediately.
|
||||
// A new independent workflow instance is created via the signal bus.
|
||||
.ContinueWith(
|
||||
"Start Transfer Process",
|
||||
new WorkflowWorkflowInvocationDeclaration
|
||||
{
|
||||
WorkflowName = "TransferPolicy",
|
||||
PayloadExpression = WorkflowExpr.Path("state"),
|
||||
});
|
||||
// Parent is now complete. TransferPolicy runs independently.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"schemaVersion": "serdica.workflow.definition/v1",
|
||||
"workflowName": "ParentWorkflow",
|
||||
"workflowVersion": "1.0.0",
|
||||
"displayName": "Parent Workflow Example",
|
||||
"workflowRoles": [],
|
||||
"start": {
|
||||
"initializeStateExpression": {
|
||||
"$type": "object",
|
||||
"properties": [
|
||||
{ "name": "policyId", "expression": { "$type": "path", "path": "start.policyId" } }
|
||||
]
|
||||
},
|
||||
"sequence": {
|
||||
"steps": [
|
||||
{
|
||||
"$type": "call-transport",
|
||||
"stepName": "Open For Change",
|
||||
"invocation": {
|
||||
"address": { "$type": "legacy-rabbit", "command": "pas_annexprocessing_alterpolicy", "mode": "Envelope" },
|
||||
"payloadExpression": {
|
||||
"$type": "object",
|
||||
"properties": [
|
||||
{ "name": "policyId", "expression": { "$type": "path", "path": "state.policyId" } }
|
||||
]
|
||||
}
|
||||
},
|
||||
"whenFailure": { "steps": [{ "$type": "complete" }] },
|
||||
"whenTimeout": { "steps": [{ "$type": "complete" }] }
|
||||
},
|
||||
{
|
||||
"$type": "sub-workflow",
|
||||
"stepName": "Review Policy Changes",
|
||||
"invocation": {
|
||||
"workflowName": "ReviewPolicyOpenForChange",
|
||||
"payloadExpression": {
|
||||
"$type": "object",
|
||||
"properties": [
|
||||
{ "name": "policyId", "expression": { "$type": "path", "path": "state.policyId" } },
|
||||
{ "name": "productCode", "expression": { "$type": "path", "path": "state.productCode" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "continue-with-workflow",
|
||||
"stepName": "Start Transfer Process",
|
||||
"invocation": {
|
||||
"workflowName": "TransferPolicy",
|
||||
"payloadExpression": { "$type": "path", "path": "state" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tasks": []
|
||||
}
|
||||
Reference in New Issue
Block a user