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>
286 lines
7.3 KiB
Markdown
286 lines
7.3 KiB
Markdown
# 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.
|
|
|