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,83 @@
using System.Collections.Generic;
using System.Text.Json;
using WorkflowEngine.Abstractions;
using WorkflowEngine.Contracts;
namespace WorkflowEngine.Tutorials;
public sealed class PolicyValidationRequest
{
public long PolicyId { get; set; }
}
public sealed class PolicyValidationWorkflow : IDeclarativeWorkflow<PolicyValidationRequest>
{
public string WorkflowName => "PolicyValidation";
public string WorkflowVersion => "1.0.0";
public string DisplayName => "Policy Validation";
public IReadOnlyCollection<string> WorkflowRoles => ["DBA"];
public WorkflowSpec<PolicyValidationRequest> Spec { get; } = WorkflowSpec
.For<PolicyValidationRequest>()
.InitializeState(WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("start.policyId"))))
.StartWith(BuildFlow)
.Build();
public IReadOnlyCollection<WorkflowTaskDescriptor> Tasks => Spec.TaskDescriptors;
private static void BuildFlow(WorkflowFlowBuilder<PolicyValidationRequest> flow)
{
flow
// --- Example 1: Simple call with shorthand error handling ---
.Call(
"Validate Policy", // step name
Address.LegacyRabbit("pas_policy_validate"), // transport address
WorkflowExpr.Object( // payload (expression-based)
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId"))),
WorkflowHandledBranchAction.Complete, // on failure: complete workflow
WorkflowHandledBranchAction.Complete) // on timeout: complete workflow
// --- Example 2: Call with typed response stored in state ---
.Call<object>(
"Load Policy Info",
Address.LegacyRabbit("pas_get_policy_product_info"),
WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId"))),
WorkflowHandledBranchAction.Complete,
WorkflowHandledBranchAction.Complete,
resultKey: "policyInfo") // store response as "policyInfo"
// Use the result to set state values
.SetIfHasValue("productCode",
WorkflowExpr.Func("upper", WorkflowExpr.Path("result.policyInfo.productCode")))
.SetIfHasValue("lob",
WorkflowExpr.Path("result.policyInfo.lob"))
// --- Example 3: Call with custom failure/timeout branches ---
.Call(
"Calculate Premium",
Address.LegacyRabbit("pas_premium_calculate_for_object",
SerdicaLegacyRabbitMode.MicroserviceConsumer),
WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId"))),
whenFailure: fail => fail
.Set("calculationFailed", WorkflowExpr.Bool(true))
.Complete(),
whenTimeout: timeout => timeout
.Set("calculationTimedOut", WorkflowExpr.Bool(true))
.Complete(),
timeoutSeconds: 120) // per-step timeout: 2 minutes
// --- Example 4: HTTP transport ---
// .Call("Notify External",
// Address.Http("authority", "/api/v1/notifications", "POST"),
// WorkflowExpr.Object(
// WorkflowExpr.Prop("message", WorkflowExpr.String("Policy validated"))),
// WorkflowHandledBranchAction.Complete,
// WorkflowHandledBranchAction.Complete)
.Complete();
}
}