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,170 @@
using System;
using System.Collections.Generic;
using WorkflowEngine.Abstractions;
using WorkflowEngine.Contracts;
namespace WorkflowEngine.Tutorials;
/// <summary>
/// Shared support helper for policy change workflows.
/// Centralizes addresses, payload builders, and reusable flow patterns.
/// </summary>
internal static class PolicyWorkflowSupport
{
// ═══════════════════════════════════════════════════════════
// ADDRESS REGISTRY
// Centralize all service routing in one place.
// ═══════════════════════════════════════════════════════════
internal static readonly LegacyRabbitAddress ValidatePolicyAddress =
new("pas_policy_validate");
internal static readonly LegacyRabbitAddress AlterPolicyAddress =
new("pas_annexprocessing_alterpolicy");
internal static readonly LegacyRabbitAddress CalculatePremiumAddress =
new("pas_premium_calculate_for_object",
SerdicaLegacyRabbitMode.MicroserviceConsumer);
internal static readonly LegacyRabbitAddress GetAnnexDescAddress =
new("pas_polannexes_get");
internal static readonly LegacyRabbitAddress NotificationEmailAddress =
new("notifications_send_email",
SerdicaLegacyRabbitMode.MicroserviceConsumer);
// ═══════════════════════════════════════════════════════════
// WORKFLOW REFERENCES
// For SubWorkflow and ContinueWith invocations.
// ═══════════════════════════════════════════════════════════
internal static readonly WorkflowReference ReviewPolicyReference =
new("ReviewPolicyOpenForChange");
internal static readonly WorkflowReference TransferPolicyReference =
new("TransferPolicy");
// ═══════════════════════════════════════════════════════════
// STATE INITIALIZATION
// Base state + override pattern for workflow families.
// ═══════════════════════════════════════════════════════════
/// <summary>
/// Builds a state initialization expression with common policy fields
/// and optional per-workflow overrides.
/// </summary>
internal static WorkflowExpressionDefinition BuildInitializeState(
params WorkflowNamedExpressionDefinition[] overrides)
{
var properties = new List<WorkflowNamedExpressionDefinition>
{
WorkflowExpr.Prop("srPolicyId", WorkflowExpr.Path("start.srPolicyId")),
WorkflowExpr.Prop("srAnnexId", WorkflowExpr.Path("start.srAnnexId")),
WorkflowExpr.Prop("srCustId", WorkflowExpr.Path("start.srCustId")),
WorkflowExpr.Prop("annexType", WorkflowExpr.Path("start.annexType")),
WorkflowExpr.Prop("beginDate", WorkflowExpr.Path("start.beginDate")),
WorkflowExpr.Prop("endDate", WorkflowExpr.Path("start.endDate")),
};
// Apply overrides: replace existing or add new properties.
foreach (var o in overrides)
{
var existing = properties.FindIndex(
p => string.Equals(p.Name, o.Name, StringComparison.OrdinalIgnoreCase));
if (existing >= 0)
{
properties[existing] = o;
}
else
{
properties.Add(o);
}
}
return WorkflowExpr.Object(properties);
}
// ═══════════════════════════════════════════════════════════
// PAYLOAD BUILDERS
// Reusable expressions for common service call payloads.
// ═══════════════════════════════════════════════════════════
internal static WorkflowExpressionDefinition BuildAlterPolicyPayload()
{
return WorkflowExpr.Object(
WorkflowExpr.Prop("srPolicyId", WorkflowExpr.Path("state.srPolicyId")),
WorkflowExpr.Prop("beginDate", WorkflowExpr.Path("state.beginDate")),
WorkflowExpr.Prop("endDate", WorkflowExpr.Path("state.endDate")),
WorkflowExpr.Prop("annexType", WorkflowExpr.Path("state.annexType")));
}
internal static WorkflowExpressionDefinition BuildAnnexTypeEquals(string type)
{
return WorkflowExpr.Eq(
WorkflowExpr.Func("upper", WorkflowExpr.Path("state.annexType")),
WorkflowExpr.String(type));
}
internal static WorkflowExpressionDefinition BuildPolicyIdPayload()
{
return WorkflowExpr.Object(
WorkflowExpr.Prop("srPolicyId", WorkflowExpr.Path("state.srPolicyId")));
}
// ═══════════════════════════════════════════════════════════
// WORKFLOW INVOCATION BUILDERS
// ═══════════════════════════════════════════════════════════
internal static WorkflowWorkflowInvocationDeclaration BuildReviewInvocation()
{
return new WorkflowWorkflowInvocationDeclaration
{
WorkflowName = ReviewPolicyReference.WorkflowName,
PayloadExpression = WorkflowExpr.Path("state"),
};
}
}
/// <summary>
/// Extension methods for common flow patterns.
/// Used across multiple workflows for DRY step sequences.
/// </summary>
internal static class PolicyWorkflowFlowExtensions
{
/// <summary>
/// Applies product info from a service call result into workflow state.
/// </summary>
internal static WorkflowFlowBuilder<T> ApplyProductInfo<T>(
this WorkflowFlowBuilder<T> flow,
string resultKey = "productInfo")
where T : class
{
return flow
.SetIfHasValue("productCode",
WorkflowExpr.Func("upper",
WorkflowExpr.Path($"result.{resultKey}.productCode")))
.SetIfHasValue("lob",
WorkflowExpr.Func("upper",
WorkflowExpr.Path($"result.{resultKey}.lob")))
.SetIfHasValue("contractType",
WorkflowExpr.Path($"result.{resultKey}.contractType"));
}
/// <summary>
/// Standard "load product info and apply" pattern.
/// </summary>
internal static WorkflowFlowBuilder<T> LoadAndApplyProductInfo<T>(
this WorkflowFlowBuilder<T> flow)
where T : class
{
return flow
.Call<object>("Load Product Info",
Address.LegacyRabbit("pas_get_policy_product_info"),
PolicyWorkflowSupport.BuildPolicyIdPayload(),
WorkflowHandledBranchAction.Complete,
WorkflowHandledBranchAction.Complete,
resultKey: "productInfo")
.ApplyProductInfo();
}
}