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:
285
docs/workflow/engine/06-implementation-structure.md
Normal file
285
docs/workflow/engine/06-implementation-structure.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# 06. Implementation Structure
|
||||
|
||||
## 1. Implementation Goal
|
||||
|
||||
The implementation should mirror the architecture instead of collapsing everything into `Services/`.
|
||||
|
||||
The code layout should make it obvious which parts are:
|
||||
|
||||
- product orchestration
|
||||
- engine runtime
|
||||
- persistence
|
||||
- signaling
|
||||
- scheduling
|
||||
- operations
|
||||
|
||||
## 2. Proposed Folder Layout
|
||||
|
||||
Recommended new structure across the workflow host, shared abstractions, and external service contracts.
|
||||
|
||||
### 2.1 Service Host Project
|
||||
|
||||
Proposed folders:
|
||||
|
||||
```text
|
||||
Engine/
|
||||
Contracts/
|
||||
Execution/
|
||||
Persistence/
|
||||
Signaling/
|
||||
Scheduling/
|
||||
Hosting/
|
||||
Diagnostics/
|
||||
```
|
||||
|
||||
Detailed proposal:
|
||||
|
||||
```text
|
||||
Engine/
|
||||
Contracts/
|
||||
IWorkflowRuntimeProvider.cs
|
||||
IWorkflowSignalBus.cs
|
||||
IWorkflowScheduleBus.cs
|
||||
IWorkflowRuntimeSnapshotStore.cs
|
||||
IWorkflowRuntimeDefinitionStore.cs
|
||||
|
||||
Execution/
|
||||
SerdicaEngineRuntimeProvider.cs
|
||||
WorkflowExecutionCoordinator.cs
|
||||
WorkflowCanonicalInterpreter.cs
|
||||
WorkflowResumePointerSerializer.cs
|
||||
WorkflowExecutionSliceResult.cs
|
||||
WorkflowWaitDescriptor.cs
|
||||
WorkflowSubWorkflowCoordinator.cs
|
||||
WorkflowTransportDispatcher.cs
|
||||
|
||||
Persistence/
|
||||
OracleWorkflowRuntimeSnapshotStore.cs
|
||||
WorkflowRuntimeSnapshotMapper.cs
|
||||
WorkflowRuntimeStateMutator.cs
|
||||
|
||||
Signaling/
|
||||
OracleAqWorkflowSignalBus.cs
|
||||
WorkflowSignalEnvelope.cs
|
||||
WorkflowSignalPump.cs
|
||||
WorkflowSignalHandler.cs
|
||||
|
||||
Scheduling/
|
||||
OracleAqWorkflowScheduleBus.cs
|
||||
WorkflowScheduleRequest.cs
|
||||
|
||||
Hosting/
|
||||
WorkflowEngineSignalHostedService.cs
|
||||
WorkflowEngineStartupValidator.cs
|
||||
|
||||
Diagnostics/
|
||||
WorkflowEngineMetrics.cs
|
||||
WorkflowEngineLogScope.cs
|
||||
```
|
||||
|
||||
### 2.2 Shared Abstractions Project
|
||||
|
||||
Keep these in abstractions:
|
||||
|
||||
- execution contracts
|
||||
- signal/schedule bus interfaces
|
||||
- runtime provider interfaces
|
||||
- runtime snapshot records where shared
|
||||
|
||||
Do not put Oracle-specific details into the shared abstractions project.
|
||||
|
||||
### 2.3 Contracts Project
|
||||
|
||||
Keep only external service contracts there.
|
||||
|
||||
Do not leak engine-internal snapshot or AQ message contracts into public workflow contracts.
|
||||
|
||||
## 3. Recommended Core Interfaces
|
||||
|
||||
### 3.1 Runtime Provider
|
||||
|
||||
```csharp
|
||||
public interface IWorkflowRuntimeProvider
|
||||
{
|
||||
string ProviderName { get; }
|
||||
|
||||
Task<WorkflowRuntimeExecutionResult> StartAsync(
|
||||
WorkflowRegistration registration,
|
||||
WorkflowDefinitionDescriptor definition,
|
||||
WorkflowBusinessReference? businessReference,
|
||||
StartWorkflowRequest request,
|
||||
object startRequest,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<WorkflowRuntimeExecutionResult> CompleteAsync(
|
||||
WorkflowRegistration registration,
|
||||
WorkflowDefinitionDescriptor definition,
|
||||
WorkflowTaskExecutionContext context,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Snapshot Store
|
||||
|
||||
```csharp
|
||||
public interface IWorkflowRuntimeSnapshotStore
|
||||
{
|
||||
Task<WorkflowRuntimeSnapshot?> GetAsync(
|
||||
string workflowInstanceId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> TryUpsertAsync(
|
||||
WorkflowRuntimeSnapshot snapshot,
|
||||
long expectedVersion,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Signal Bus
|
||||
|
||||
```csharp
|
||||
public interface IWorkflowSignalBus
|
||||
{
|
||||
Task PublishAsync(
|
||||
WorkflowSignalEnvelope envelope,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IWorkflowSignalLease?> ReceiveAsync(
|
||||
string consumerName,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Schedule Bus
|
||||
|
||||
```csharp
|
||||
public interface IWorkflowScheduleBus
|
||||
{
|
||||
Task ScheduleAsync(
|
||||
WorkflowSignalEnvelope envelope,
|
||||
DateTime dueAtUtc,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 Definition Store
|
||||
|
||||
```csharp
|
||||
public interface IWorkflowRuntimeDefinitionStore
|
||||
{
|
||||
WorkflowRuntimeDefinition GetRequiredDefinition(
|
||||
string workflowName,
|
||||
string workflowVersion);
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Runtime Definition Normalization
|
||||
|
||||
Recommended startup path:
|
||||
|
||||
1. read registrations from `WorkflowRegistrationCatalog`
|
||||
2. compile each workflow to canonical definition
|
||||
3. validate canonical definition
|
||||
4. convert to `WorkflowRuntimeDefinition`
|
||||
5. store in immutable in-memory cache
|
||||
|
||||
This startup step should be implemented once and reused by:
|
||||
|
||||
- runtime execution
|
||||
- canonical inspection endpoints
|
||||
- diagnostics
|
||||
|
||||
## 5. Snapshot Model
|
||||
|
||||
Recommended runtime snapshot record:
|
||||
|
||||
```csharp
|
||||
public sealed record WorkflowRuntimeSnapshot
|
||||
{
|
||||
public required string WorkflowInstanceId { get; init; }
|
||||
public required string WorkflowName { get; init; }
|
||||
public required string WorkflowVersion { get; init; }
|
||||
public required string RuntimeProvider { get; init; }
|
||||
public required long Version { get; init; }
|
||||
public WorkflowBusinessReference? BusinessReference { get; init; }
|
||||
public required string RuntimeStatus { get; init; }
|
||||
public required WorkflowEngineState EngineState { get; init; }
|
||||
public DateTime CreatedOnUtc { get; init; }
|
||||
public DateTime? CompletedOnUtc { get; init; }
|
||||
public DateTime LastUpdatedOnUtc { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
## 6. AQ Adapter Design
|
||||
|
||||
AQ adapters should be isolated behind backend-neutral interfaces.
|
||||
|
||||
Do not let the rest of the engine know about:
|
||||
|
||||
- queue table names
|
||||
- enqueue option types
|
||||
- dequeue option types
|
||||
- AQ-specific exception types
|
||||
|
||||
That isolation is the main swap seam for any future non-AQ backend.
|
||||
|
||||
## 7. Transaction Boundary Design
|
||||
|
||||
### 7.1 Coordinator Owns Transactions
|
||||
|
||||
`WorkflowExecutionCoordinator` should own the unit of work for:
|
||||
|
||||
- snapshot update
|
||||
- projection update
|
||||
- AQ publish
|
||||
- AQ dequeue completion
|
||||
|
||||
This avoids split responsibility across product services and engine helpers.
|
||||
|
||||
### 7.2 Projection Store Remains Focused
|
||||
|
||||
`WorkflowProjectionStore` should stay focused on:
|
||||
|
||||
- read projection writes
|
||||
- query paths
|
||||
- task event history
|
||||
|
||||
It should not become the coordinator for AQ or engine versioning.
|
||||
|
||||
## 8. Startup Composition
|
||||
|
||||
`WorkflowServiceCollectionExtensions` should eventually compose the engine roughly like this:
|
||||
|
||||
```csharp
|
||||
services.Configure<WorkflowRuntimeOptions>(...);
|
||||
services.Configure<WorkflowEngineOptions>(...);
|
||||
services.Configure<WorkflowAqOptions>(...);
|
||||
|
||||
services.AddScoped<IWorkflowRuntimeProvider, SerdicaEngineRuntimeProvider>();
|
||||
services.AddScoped<IWorkflowRuntimeOrchestrator, WorkflowRuntimeOrchestrator>();
|
||||
services.AddScoped<IWorkflowRuntimeSnapshotStore, OracleWorkflowRuntimeSnapshotStore>();
|
||||
services.AddScoped<IWorkflowSignalBus, OracleAqWorkflowSignalBus>();
|
||||
services.AddScoped<IWorkflowScheduleBus, OracleAqWorkflowScheduleBus>();
|
||||
services.AddSingleton<IWorkflowRuntimeDefinitionStore, WorkflowRuntimeDefinitionStore>();
|
||||
services.AddHostedService<WorkflowEngineSignalHostedService>();
|
||||
```
|
||||
|
||||
## 9. Avoided Anti-Patterns
|
||||
|
||||
The implementation should explicitly avoid:
|
||||
|
||||
- a giant engine service that knows everything
|
||||
- polling tables for due work
|
||||
- in-memory only timer ownership
|
||||
- transport-specific engine branches scattered across the codebase
|
||||
- storing huge snapshots in AQ messages
|
||||
- mixing public contracts with engine internal contracts
|
||||
|
||||
## 10. Implementation Rules
|
||||
|
||||
1. Put backend-specific code behind an interface.
|
||||
2. Keep canonical interpretation pure and backend-agnostic.
|
||||
3. Keep Oracle transaction handling close to the execution coordinator.
|
||||
4. Make resume idempotency part of the snapshot model, not a side utility.
|
||||
5. Keep projection writes product-oriented, not runtime-oriented.
|
||||
|
||||
Reference in New Issue
Block a user