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:
master
2026-03-20 19:14:44 +02:00
parent e56f9a114a
commit f5b5f24d95
422 changed files with 85428 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
using System.Collections.Generic;
using WorkflowEngine.Abstractions;
using WorkflowEngine.Contracts;
namespace WorkflowEngine.Tutorials;
public sealed class PolicyRoutingRequest
{
public long PolicyId { get; set; }
public string AnnexType { get; set; } = string.Empty;
public bool PolicyExistsOnIPAL { get; set; }
}
public sealed class PolicyRoutingWorkflow : IDeclarativeWorkflow<PolicyRoutingRequest>
{
public string WorkflowName => "PolicyRouting";
public string WorkflowVersion => "1.0.0";
public string DisplayName => "Policy Routing Example";
public IReadOnlyCollection<string> WorkflowRoles => ["DBA"];
public WorkflowSpec<PolicyRoutingRequest> Spec { get; } = WorkflowSpec
.For<PolicyRoutingRequest>()
.InitializeState(WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("start.policyId")),
WorkflowExpr.Prop("annexType", WorkflowExpr.Path("start.annexType")),
WorkflowExpr.Prop("policyExistsOnIPAL",
WorkflowExpr.Func("coalesce",
WorkflowExpr.Path("start.policyExistsOnIPAL"),
WorkflowExpr.Bool(true)))))
.StartWith(BuildFlow)
.Build();
public IReadOnlyCollection<WorkflowTaskDescriptor> Tasks => Spec.TaskDescriptors;
private static void BuildFlow(WorkflowFlowBuilder<PolicyRoutingRequest> flow)
{
// --- Example 1: State flag decision (boolean shorthand) ---
flow.WhenStateFlag(
"policyExistsOnIPAL", // state key to check
true, // expected value
"Policy exists on IPAL?", // decision name (appears in diagram)
whenTrue: ipal => ipal
// --- Example 2: Expression decision ---
.WhenExpression(
"Annex Type?",
WorkflowExpr.Eq(
WorkflowExpr.Func("upper", WorkflowExpr.Path("state.annexType")),
WorkflowExpr.String("BENEF")),
benefit => benefit
.Set("route", WorkflowExpr.String("BENEFIT_PROCESSING"))
.Complete(),
// --- Example 3: Nested decision ---
other => other.WhenExpression(
"Is Equipment?",
WorkflowExpr.Eq(
WorkflowExpr.Func("upper", WorkflowExpr.Path("state.annexType")),
WorkflowExpr.String("ADDEQ")),
equipment => equipment
.Set("route", WorkflowExpr.String("EQUIPMENT_PROCESSING"))
.Complete(),
cover => cover
.Set("route", WorkflowExpr.String("COVER_CHANGE"))
.Complete())),
whenElse: notIpal => notIpal
.Set("route", WorkflowExpr.String("INSIS_PROCESSING"))
.Complete());
}
}