# 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 StartAsync( WorkflowRegistration registration, WorkflowDefinitionDescriptor definition, WorkflowBusinessReference? businessReference, StartWorkflowRequest request, object startRequest, CancellationToken cancellationToken = default); Task CompleteAsync( WorkflowRegistration registration, WorkflowDefinitionDescriptor definition, WorkflowTaskExecutionContext context, CancellationToken cancellationToken = default); } ``` ### 3.2 Snapshot Store ```csharp public interface IWorkflowRuntimeSnapshotStore { Task GetAsync( string workflowInstanceId, CancellationToken cancellationToken = default); Task TryUpsertAsync( WorkflowRuntimeSnapshot snapshot, long expectedVersion, CancellationToken cancellationToken = default); } ``` ### 3.3 Signal Bus ```csharp public interface IWorkflowSignalBus { Task PublishAsync( WorkflowSignalEnvelope envelope, CancellationToken cancellationToken = default); Task 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(...); services.Configure(...); services.Configure(...); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddSingleton(); services.AddHostedService(); ``` ## 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.