EricksonLopez.Processes.Storage.Oracle
1.0.0
dotnet add package EricksonLopez.Processes.Storage.Oracle --version 1.0.0
NuGet\Install-Package EricksonLopez.Processes.Storage.Oracle -Version 1.0.0
<PackageReference Include="EricksonLopez.Processes.Storage.Oracle" Version="1.0.0" />
<PackageVersion Include="EricksonLopez.Processes.Storage.Oracle" Version="1.0.0" />
<PackageReference Include="EricksonLopez.Processes.Storage.Oracle" />
paket add EricksonLopez.Processes.Storage.Oracle --version 1.0.0
#r "nuget: EricksonLopez.Processes.Storage.Oracle, 1.0.0"
#:package EricksonLopez.Processes.Storage.Oracle@1.0.0
#addin nuget:?package=EricksonLopez.Processes.Storage.Oracle&version=1.0.0
#tool nuget:?package=EricksonLopez.Processes.Storage.Oracle&version=1.0.0
EricksonLopez.Processes
High-performance, trimming-safe, and Native AOT-ready Process Manager and Saga orchestration ecosystem for modern .NET.
EricksonLopez.Processes is an enterprise-grade, zero-reflection Process Manager and distributed Saga orchestration ecosystem engineered specifically for high-throughput, event-driven architectures in .NET 10+. It eliminates heavy in-memory workflow runtimes, distributed locking contention, and reflection overhead by combining deterministic Optimistic Concurrency Control (OCC / CAS with monotonic Revision tokens), zero-allocation value types, compile-time Roslyn source generation, multi-database persistence adapters, and native OpenTelemetry observability into an AOT-first foundation.
Table of Contents
- What Problem It Solves
- Key Features
- Ecosystem
- Documentation
- Installation
- Quick Start
- Core Use Cases
- Use Case 1: Clean Architecture / Event-Driven Order Fulfillment Saga
- Use Case 2: Multi-Step Onboarding with OCC CAS Coordination
- Use Case 3: Outbox-Backed Reliable Side-Effect Publication
- Use Case 4: Reverse-Order Compensation for Distributed Rollbacks (LIFO)
- Use Case 5: Zero-Downtime State Schema Evolution
- Use Case 6: Deterministic In-Memory Testing & Chaos Simulation
- Configuration & Integrations
- Testing & Quality
- Performance Benchmarks
- Compatibility & Technical Matrix
- Architecture & Design Principles
- Best Practices & Anti-Patterns
- Troubleshooting & Common Pitfalls
- Part of the EricksonLopez Ecosystem
- Contributing
- License
π― What Problem It Solves
The Traditional Workflow Dilemma
Building stateful workflows, distributed sagas, and multi-step business coordination in microservices and distributed systems traditionally suffers from critical architectural pitfalls:
- The Heavyweight Runtime Tax: Traditional workflow orchestrators (e.g., BPMN engines, persistent orchestrator actors) hold workflow execution state in memory, pinning OS threads and consuming massive heap allocations for long-lived processes.
- Concurrency Hazards & Distributed Locking: High-throughput distributed event streams cause race conditions when concurrent events arrive for the same workflow instance. Teams often resort to heavy distributed locks (Redis Redlock, Consul, database row locks), degrading throughput and introducing deadlock vulnerabilities.
- Magical Rollbacks vs Distributed Realities: In distributed microservice architectures, true atomic ACID 2-phase commits across autonomous databases are an anti-pattern. Workflows require explicit, deterministic compensating transactions executed in strict reverse dependency order (LIFO).
- Reflection Overhead & Native AOT Incompatibility: Most existing saga libraries rely heavily on runtime reflection (
Activator.CreateInstance,Assembly.GetTypes(), dynamic proxy interceptors), which prevents compilation with Native AOT and triggers fatal trimming warnings. - Infrastructure Coupling & Leakage: Domain workflows frequently become tightly coupled to specific message brokers (RabbitMQ, Kafka, Azure Service Bus) or persistence frameworks (EF Core, ORMs), making pure unit testing impossible.
How EricksonLopez.Processes Solves This
- Persist State, Never the Runtime: Workflows do not stay resident in memory. Upon an incoming event trigger, the instance state hydrates from durable storage, applies pure deterministic transitions, persists state via atomic CAS tokens (
Revision), emits outbound intent effects (commands/events), and suspends or completes. - Lock-Free Optimistic Concurrency Control (OCC CAS): State transitions use atomic monotonic
Revisiontokens with automatic linear/exponential backoff retry loops, achieving sub-microsecond state transitions under high concurrency without database locks. - First-Class Reverse-Order Compensation (LIFO): Compensating actions are recorded alongside forward steps with immutable payloads and dispatched sequentially in reverse order (
CompensatingβCompensatedorFailed). - 100% Native AOT & Trimming Compliance: Zero runtime reflection. Roslyn Incremental Source Generators register process definitions and build DI tables at compile time, accompanied by Roslyn Analyzers enforcing transition correctness.
- Pure Domain Isolation (Clean Architecture): The core domain and abstractions have zero external dependencies. Workflows produce pure side-effect intents (
ProcessEffect.Command,ProcessEffect.Event,ProcessEffect.Timeout), leaving network transport and broker dispatching to dedicated perimeter adapters.
β‘ Key Features
- ποΈ Ultra-Low Latency & Zero-Allocation Identifiers: Struct-based value types (
ProcessId,Revision,CorrelationId,ProcessVersion,CausationId) implementingISpanParsable<T>andISpanFormattableformat directly into stack buffers with 0 B heap allocations. - π Deterministic Optimistic Concurrency Control: Monotonic revision tokens prevent lost updates across concurrent workers without distributed locking.
- π Explicit Reverse-Order Compensation Engine: Full support for distributed sagas with automated LIFO compensation rollbacks and failure escalation.
- βοΈ Compile-Time Roslyn Source Generator: Auto-discovers
[ProcessDefinition]and[SagaDefinition]classes to generate AOT-safe dependency injection extensions (AddGeneratedProcesses()). - π‘οΈ Roslyn Diagnostic Analyzers: Real-time compile-time inspection verifying state machine completeness (
PROC001) and compensation coverage (PROC002). - π Deep OpenTelemetry Observability: Built-in
ActivitySourcetracing andSystem.Diagnostics.Metricsreporting total executions, OCC retry counts, effect emissions, and execution latency. - ποΈ Multi-Database Persistent Adapters: Production-ready storage adapters for PostgreSQL (JSONB), SQL Server, SQLite, MySQL, MariaDB, and Oracle.
- π Seamless Ecosystem Integrations: Native bridge packages for
EricksonLopez.Events,EricksonLopez.Mediator, andEricksonLopez.Outbox. - 𧬠Zero-Downtime Schema Evolution: Fluent
ProcessStateMigrationPipelinefor deterministic version upgrades across evolving state schemas.
π¦ Ecosystem
| Package | Version | Description |
|---|---|---|
EricksonLopez.Processes.Abstractions |
Pure abstractions, strongly typed identifiers, state contracts, and store interfaces. Zero external dependencies. | |
EricksonLopez.Processes |
Core Process Manager and Saga execution engine, compensation runner, and correlation primitives. | |
EricksonLopez.Processes.Generator |
Roslyn incremental source generator for compile-time registration and AddGeneratedProcesses() DI extension. |
|
EricksonLopez.Processes.Analyzers |
Roslyn analyzers validating state machine completeness, unhandled transition compensation, and process invariants. | |
EricksonLopez.Processes.DependencyInjection |
IServiceCollection extension methods for registering coordinators and stores. |
|
EricksonLopez.Processes.SystemTextJson |
System.Text.Json AOT-compatible serialization helpers and converters for process identifiers. | |
EricksonLopez.Processes.Events |
Event dispatching integration bridging process manager effects to EricksonLopez.Events.Contracts. |
|
EricksonLopez.Processes.Mediator |
In-process mediator dispatching integration bridging effects to EricksonLopez.Mediator. |
|
EricksonLopez.Processes.Outbox |
Outbox pattern integration dispatching effects reliably via EricksonLopez.Outbox. |
|
EricksonLopez.Processes.Storage.PostgreSql |
High-performance PostgreSQL persistence provider using Npgsql and JSONB state storage. | |
EricksonLopez.Processes.Storage.SqlServer |
High-performance SQL Server persistence provider using Microsoft.Data.SqlClient and JSON state storage. | |
EricksonLopez.Processes.Storage.Sqlite |
High-performance SQLite persistence provider using Microsoft.Data.Sqlite and JSON state storage. | |
EricksonLopez.Processes.Storage.MySql |
High-performance MySQL persistence provider using MySqlConnector and JSON state storage. | |
EricksonLopez.Processes.Storage.MariaDb |
High-performance MariaDB persistence provider using MySqlConnector and JSON state storage. | |
EricksonLopez.Processes.Storage.Oracle |
High-performance Oracle persistence provider using Oracle.ManagedDataAccess.Core and JSON state storage. | |
EricksonLopez.Processes.Testing |
Testing utilities, doubles, InMemory test store with atomic CAS, and chaos fault injectors. |
π Documentation
π Official Documentation Hub: https://github.com/ericksonlopezf/dotnet-processes/tree/main/docs
π Interactive Showcase (Levels 00 to 03)
| Level | Topic | Description |
|---|---|---|
| Level 00 | Introduction & Architecture | Core philosophy, execution model, and zero-reflection foundations. |
| Level 01 | State Machines & Sagas | Modeling multi-step workflows, transitions, and LIFO compensation. |
| Level 02 | Storage & Durability | Persistent stores, optimistic concurrency control (OCC), and revision tokens. |
| Level 03 | Zero-Allocation & Native AOT | Performance benchmarks, span parsing, and source generator setup. |
π Technical Reference & Architecture Guides
- Architecture & Diagrams β Visual reference for package boundaries, OCC loops, FSM lifecycles, and telemetry flow.
- Architecture Overview β System overview, coordinator lifecycle, and state hydration patterns.
- State Machine Lifecycle β Formal transition semantics and terminal state guarantees.
- API Reference β Exhaustive technical specification for all public types, records, and interfaces.
- Cookbook & Recipes β Production-tested recipes for PostgreSQL, Outbox, composite keys, and Native AOT.
- Building Sagas Guide β Comprehensive guide to distributed saga orchestration and compensation modeling.
- PostgreSQL + Dapper Store β Setup and schema guidelines for PostgreSQL persistence.
- Performance Guide β Zero-allocation techniques, span formatting, and benchmark throughput analysis.
- State Migration Guide β Zero-downtime schema evolution using
ProcessStateMigrationPipeline. - Troubleshooting Guide β Common errors, concurrency conflict resolution, and diagnostic steps.
- Best Practices & Guidelines β Immutable state design, pure transitions, and saga rollback rules.
- Feature Matrix & Decisions β Detailed 55-feature evaluation, capabilities, and design decisions.
- CI/CD & Quality Gates β GitHub Actions pipelines, Stryker mutation gates, and Native AOT smoke testing.
- Architectural Decision Records (ADRs) β Index of 40 formal ADRs documenting all system design choices.
- Competitor Analysis β In-depth architectural comparison vs MassTransit, Elsa, Temporal, and Dapr.
- Boundary Specification β Zero-dependency invariants for
EricksonLopez.Processes.Abstractions.
π₯ Installation
Install the packages via the .NET CLI or Package Manager Console based on your architectural requirements.
1. Core Engine & Abstractions (Required)
# Pure abstractions, IDs, and contracts (zero dependencies)
dotnet add package EricksonLopez.Processes.Abstractions
# Core execution engine, coordinator, and compensation runner
dotnet add package EricksonLopez.Processes
2. Source Generator & Analyzers (Recommended for AOT)
# Roslyn incremental source generator for zero-reflection DI
dotnet add package EricksonLopez.Processes.Generator --private-assets all
# Roslyn compile-time analyzers for state machine and saga validation
dotnet add package EricksonLopez.Processes.Analyzers --private-assets all
3. Dependency Injection & Serialization
# Microsoft.Extensions.DependencyInjection integration
dotnet add package EricksonLopez.Processes.DependencyInjection
# System.Text.Json AOT-safe serialization converters
dotnet add package EricksonLopez.Processes.SystemTextJson
4. Storage Providers (Choose Your Database)
# PostgreSQL (JSONB)
dotnet add package EricksonLopez.Processes.Storage.PostgreSql
# Microsoft SQL Server
dotnet add package EricksonLopez.Processes.Storage.SqlServer
# SQLite
dotnet add package EricksonLopez.Processes.Storage.Sqlite
# MySQL / MariaDB
dotnet add package EricksonLopez.Processes.Storage.MySql
dotnet add package EricksonLopez.Processes.Storage.MariaDb
# Oracle Database
dotnet add package EricksonLopez.Processes.Storage.Oracle
5. Ecosystem Integrations & Testing
# Outbox pattern integration
dotnet add package EricksonLopez.Processes.Outbox
# Mediator bridge
dotnet add package EricksonLopez.Processes.Mediator
# Domain events bridge
dotnet add package EricksonLopez.Processes.Events
# In-memory test store & fault injection doubles
dotnet add package EricksonLopez.Processes.Testing
π Quick Start
Follow these 5 progressive steps to model, execute, and persist your first distributed saga.
1. Define State, Events, and Commands
Define your immutable process state as a sealed record and declare your domain events and side-effect commands.
using EricksonLopez.Processes.Abstractions;
// 1. Immutable domain state schema
public sealed record OrderSagaState(
string OrderId,
string CustomerId,
decimal Amount,
bool PaymentCaptured,
bool InventoryReserved) : IProcessState;
// 2. Domain event triggers
public sealed record OrderPlacedEvent(Guid OrderId, string CustomerId, decimal Amount);
public sealed record PaymentAuthorizedEvent(Guid OrderId);
public sealed record InventoryExhaustedEvent(Guid OrderId, string Reason);
// 3. Side-effect command intents emitted by the saga
public sealed record CapturePaymentCommand(Guid OrderId, decimal Amount);
public sealed record ReserveInventoryCommand(Guid OrderId);
public sealed record RefundPaymentCommand(Guid OrderId, decimal Amount);
2. Implement the Saga with Compensation
Decorate your saga with [SagaDefinition] and implement ISaga<TState>, ICompensationHandler<TState>, and IProcessHandler<TState, TEvent> for each incoming event.
using EricksonLopez.Processes;
using EricksonLopez.Processes.Abstractions;
[SagaDefinition("order.fulfillment", 1)]
public sealed class OrderFulfillmentSaga :
ISaga<OrderSagaState>,
ICompensationHandler<OrderSagaState>,
IProcessHandler<OrderSagaState, OrderPlacedEvent>,
IProcessHandler<OrderSagaState, PaymentAuthorizedEvent>,
IProcessHandler<OrderSagaState, InventoryExhaustedEvent>
{
public ProcessType Type => ProcessType.From("order.fulfillment");
public ProcessVersion Version => ProcessVersion.Initial;
// Step 1: Handle OrderPlaced -> emit CapturePaymentCommand
public ValueTask<ProcessTransitionResult<OrderSagaState>> HandleAsync(
OrderSagaState state,
OrderPlacedEvent @event,
ProcessContext context)
{
var updated = state with
{
OrderId = @event.OrderId.ToString(),
CustomerId = @event.CustomerId,
Amount = @event.Amount
};
var effect = new ProcessEffect.Command(new CapturePaymentCommand(@event.OrderId, @event.Amount));
return ValueTask.FromResult(ProcessTransitionResult<OrderSagaState>.Advance(
updated,
ProcessStatus.Running,
effects: [effect]));
}
// Step 2: Handle PaymentAuthorized -> record compensation milestone and emit ReserveInventoryCommand
public ValueTask<ProcessTransitionResult<OrderSagaState>> HandleAsync(
OrderSagaState state,
PaymentAuthorizedEvent @event,
ProcessContext context)
{
var updated = state with { PaymentCaptured = true };
var effect = new ProcessEffect.Command(new ReserveInventoryCommand(@event.OrderId));
// Record forward step to allow automatic reverse LIFO compensation if later steps fail
var compensation = new CompensationStep("CapturePayment", new { Amount = state.Amount }, context.Now);
return ValueTask.FromResult(ProcessTransitionResult<OrderSagaState>.Advance(
updated,
ProcessStatus.Running,
effects: [effect],
recordedCompensations: [compensation]));
}
// Step 3: Handle InventoryExhausted -> trigger reverse-order compensation
public ValueTask<ProcessTransitionResult<OrderSagaState>> HandleAsync(
OrderSagaState state,
InventoryExhaustedEvent @event,
ProcessContext context)
{
return ValueTask.FromResult(ProcessTransitionResult<OrderSagaState>.Compensate(
state,
compensationActions: [
new CompensationAction("CapturePayment", new { Amount = state.Amount })
],
reason: $"Inventory reservation failed: {@event.Reason}"));
}
// Execute compensating actions in reverse order
public ValueTask<ProcessTransitionResult<OrderSagaState>> CompensateAsync(
OrderSagaState state,
CompensationAction action,
ProcessContext context)
{
var updated = action.StepName switch
{
"CapturePayment" => state with { PaymentCaptured = false },
_ => state
};
var effect = new ProcessEffect.Command(
new RefundPaymentCommand(Guid.Parse(state.OrderId), state.Amount));
return ValueTask.FromResult(ProcessTransitionResult<OrderSagaState>.Advance(
updated,
ProcessStatus.Compensating,
effects: [effect]));
}
}
3. Execute via ProcessCoordinator with Correlation
Extract correlation deterministic keys using IProcessCorrelation<TEvent> and drive execution through ProcessCoordinator<TState>.
using EricksonLopez.Processes;
using EricksonLopez.Processes.Abstractions;
using EricksonLopez.Processes.Testing;
// 1. Define correlation strategy for the initiating event
public sealed class OrderPlacedCorrelation : IProcessCorrelation<OrderPlacedEvent>
{
public ProcessId ExtractProcessId(OrderPlacedEvent @event) => ProcessId.From(@event.OrderId);
public CorrelationId ExtractCorrelationId(OrderPlacedEvent @event) => CorrelationId.From(@event.OrderId.ToString());
}
// 2. Initialize store and coordinator with custom OCC options
var store = new InMemoryProcessStore<OrderSagaState>();
var options = new ProcessCoordinatorOptions
{
MaxConcurrencyRetries = 3,
InitialBackoffDelay = TimeSpan.FromMilliseconds(50)
};
var coordinator = new ProcessCoordinator<OrderSagaState>(store, options);
var saga = new OrderFulfillmentSaga();
// 3. Execute the initiating event
var initialEvent = new OrderPlacedEvent(Guid.NewGuid(), "CUST-9420", 250.00m);
var result = await coordinator.ExecuteAsync(
handler: saga,
correlation: new OrderPlacedCorrelation(),
eventMessage: initialEvent,
initialStateFactory: e => new OrderSagaState(e.OrderId.ToString(), e.CustomerId, e.Amount, false, false),
canInitiate: true);
Console.WriteLine($"Status: {result.Instance.Status}"); // Running
Console.WriteLine($"Emitted Effects: {result.Effects.Count}"); // 1 (CapturePaymentCommand)
4. Zero-Boilerplate DI Registration via Source Generator
Leverage EricksonLopez.Processes.Generator to emit compile-time service registrations without runtime reflection.
// Program.cs
using EricksonLopez.Processes.DependencyInjection;
using EricksonLopez.Processes.Generated;
using EricksonLopez.Processes.Storage.PostgreSql;
var builder = WebApplication.CreateBuilder(args);
// Compile-time auto-generated discovery of all [SagaDefinition] / [ProcessDefinition] classes
builder.Services.AddGeneratedProcesses();
// Register coordinator and PostgreSQL persistence adapter
builder.Services
.AddProcesses()
.AddProcessCoordinator<OrderSagaState>(options =>
{
options.MaxConcurrencyRetries = 5;
options.InitialBackoffDelay = TimeSpan.FromMilliseconds(25);
})
.AddPostgreSqlProcessStore<OrderSagaState>(
connectionString: builder.Configuration.GetConnectionString("ProcessesDatabase")!,
tableName: "order_sagas");
var app = builder.Build();
app.Run();
5. Schema Evolution & Version Migration Pipeline
Evolve persisted state schemas across application versions without database lockouts or data corruption using ProcessStateMigrationPipeline.
using EricksonLopez.Processes;
using EricksonLopez.Processes.Abstractions;
public sealed record OrderStateV1(string OrderId, decimal Amount) : IProcessState;
public sealed record OrderStateV2(string OrderId, decimal Amount, string Currency) : IProcessState;
public sealed record OrderStateV3(string OrderId, decimal Amount, string Currency, bool IsPriority) : IProcessState;
// Compose multi-step migration pipeline: v1 -> v2 -> v3
var migrator = ProcessStateMigrationPipeline.Create<OrderStateV1>(ProcessVersion.From(1))
.AddStep(ProcessVersion.From(2), v1 => new OrderStateV2(v1.OrderId, v1.Amount, "USD"))
.AddStep(ProcessVersion.From(3), v2 => new OrderStateV3(v2.OrderId, v2.Amount, v2.Currency, IsPriority: v2.Amount > 1000m))
.Build<OrderStateV1>();
var oldState = new OrderStateV1("ORD-100", 1500m);
var migrated = migrator.Migrate(oldState);
Console.WriteLine($"Migrated v3: {migrated.OrderId}, Currency: {migrated.Currency}, Priority: {migrated.IsPriority}");
π‘ Core Use Cases
Use Case 1: Clean Architecture / Event-Driven Order Fulfillment Saga
Coordinate multi-service e-commerce fulfillment with discrete command emissions and decoupled domain boundaries.
[SagaDefinition("sales.order_fulfillment", 1)]
public sealed class OrderFulfillmentCoordinator :
ISaga<OrderFulfillmentState>,
ICompensationHandler<OrderFulfillmentState>,
IProcessHandler<OrderFulfillmentState, OrderCreatedDomainEvent>,
IProcessHandler<OrderFulfillmentState, PaymentSettledDomainEvent>,
IProcessHandler<OrderFulfillmentState, InventoryDepletedDomainEvent>
{
public ProcessType Type => ProcessType.From("sales.order_fulfillment");
public ProcessVersion Version => ProcessVersion.Initial;
public ValueTask<ProcessTransitionResult<OrderFulfillmentState>> HandleAsync(
OrderFulfillmentState state,
OrderCreatedDomainEvent @event,
ProcessContext context)
{
var newState = state with
{
OrderId = @event.OrderId,
TotalAmount = @event.TotalAmount,
CustomerId = @event.CustomerId
};
return ValueTask.FromResult(ProcessTransitionResult<OrderFulfillmentState>.Advance(
newState,
ProcessStatus.Running,
effects: [new ProcessEffect.Command(new ChargeCustomerCommand(@event.OrderId, @event.TotalAmount))]));
}
public ValueTask<ProcessTransitionResult<OrderFulfillmentState>> HandleAsync(
OrderFulfillmentState state,
PaymentSettledDomainEvent @event,
ProcessContext context)
{
var newState = state with { IsPaid = true };
var compensation = new CompensationStep("PaymentCapture", new { Amount = state.TotalAmount }, context.Now);
return ValueTask.FromResult(ProcessTransitionResult<OrderFulfillmentState>.Advance(
newState,
ProcessStatus.Running,
effects: [new ProcessEffect.Command(new ReserveWarehouseStockCommand(state.OrderId))],
recordedCompensations: [compensation]));
}
public ValueTask<ProcessTransitionResult<OrderFulfillmentState>> HandleAsync(
OrderFulfillmentState state,
InventoryDepletedDomainEvent @event,
ProcessContext context)
{
return ValueTask.FromResult(ProcessTransitionResult<OrderFulfillmentState>.Compensate(
state,
compensationActions: [new CompensationAction("PaymentCapture", new { Amount = state.TotalAmount })],
reason: "Out of stock in all regional fulfillment centers."));
}
public ValueTask<ProcessTransitionResult<OrderFulfillmentState>> CompensateAsync(
OrderFulfillmentState state,
CompensationAction action,
ProcessContext context)
{
return ValueTask.FromResult(ProcessTransitionResult<OrderFulfillmentState>.Advance(
state with { IsPaid = false },
ProcessStatus.Compensating,
effects: [new ProcessEffect.Command(new IssuePaymentRefundCommand(state.OrderId, state.TotalAmount))]));
}
}
Use Case 2: Multi-Step Onboarding with OCC CAS Coordination
Handle concurrent identity verification, credit checks, and account setup safely under high concurrent webhook deliveries.
public sealed record UserOnboardingState(
string UserId,
bool EmailVerified,
bool IdentityPassed,
bool KycCleared) : IProcessState;
[ProcessDefinition("customer.onboarding", 1)]
public sealed class UserOnboardingProcess :
IProcessDefinition<UserOnboardingState>,
IProcessHandler<UserOnboardingState, EmailVerifiedEvent>,
IProcessHandler<UserOnboardingState, KycApprovedEvent>
{
public ProcessType Type => ProcessType.From("customer.onboarding");
public ProcessVersion Version => ProcessVersion.Initial;
public ValueTask<ProcessTransitionResult<UserOnboardingState>> HandleAsync(
UserOnboardingState state,
EmailVerifiedEvent @event,
ProcessContext context)
{
var updated = state with { EmailVerified = true };
return EvaluateCompletion(updated);
}
public ValueTask<ProcessTransitionResult<UserOnboardingState>> HandleAsync(
UserOnboardingState state,
KycApprovedEvent @event,
ProcessContext context)
{
var updated = state with { KycCleared = true, IdentityPassed = true };
return EvaluateCompletion(updated);
}
private static ValueTask<ProcessTransitionResult<UserOnboardingState>> EvaluateCompletion(UserOnboardingState state)
{
if (state.EmailVerified && state.KycCleared)
{
var effect = new ProcessEffect.Event(new UserOnboardingCompletedEvent(state.UserId));
return ValueTask.FromResult(ProcessTransitionResult<UserOnboardingState>.Complete(state, effects: [effect]));
}
return ValueTask.FromResult(ProcessTransitionResult<UserOnboardingState>.Advance(state, ProcessStatus.Running));
}
}
Use Case 3: Outbox-Backed Reliable Side-Effect Publication
Bridge process side-effect intents directly to transactional outbox tables for at-least-once guaranteed delivery to Apache Kafka or RabbitMQ.
using EricksonLopez.Processes.Outbox;
public sealed class OrderSagaEndpoint
{
private readonly ProcessCoordinator<OrderSagaState> _coordinator;
private readonly IProcessOutboxDispatcher _outboxDispatcher;
private readonly OrderFulfillmentSaga _saga;
public OrderSagaEndpoint(
ProcessCoordinator<OrderSagaState> coordinator,
IProcessOutboxDispatcher outboxDispatcher,
OrderFulfillmentSaga saga)
{
_coordinator = coordinator;
_outboxDispatcher = outboxDispatcher;
_saga = saga;
}
public async Task HandleIncomingEventAsync(OrderPlacedEvent @event, CancellationToken ct)
{
// 1. Execute state transition and OCC CAS commit
var result = await _coordinator.ExecuteAsync(
handler: _saga,
correlation: new OrderPlacedCorrelation(),
eventMessage: @event,
initialStateFactory: e => new OrderSagaState(e.OrderId.ToString(), e.CustomerId, e.Amount, false, false),
canInitiate: true,
cancellationToken: ct);
// 2. Atomically enqueue emitted side effects into the transactional outbox
if (result.Effects.Count > 0)
{
await _outboxDispatcher.DispatchAsync(result.Effects, ct);
}
}
}
Use Case 4: Reverse-Order Compensation for Distributed Rollbacks (LIFO)
Ensure completed milestone steps are undone in strict reverse chronological sequence during partial distributed failures.
// The SagaCompensationEngine handles automated LIFO unwinding
var compensationEngine = new SagaCompensationEngine();
var recordedMilestones = new List<CompensationAction>
{
new("Step1_AuthorizePayment", new { Amount = 500m }),
new("Step2_ReserveInventory", new { Sku = "SKU-990", Quantity = 2 }),
new("Step3_BookShippingCourier", new { TrackingId = "TRK-001" })
};
// Compensation executes in reverse: Step3 -> Step2 -> Step1
var compensationResult = await compensationEngine.ExecuteCompensationAsync(
handler: mySagaHandler,
compensationActions: recordedMilestones,
initialState: currentState,
context: processContext);
Console.WriteLine($"Rollback Outcome: {compensationResult.Status}"); // Compensated
Use Case 5: Zero-Downtime State Schema Evolution
Upgrade long-running multi-day workflows seamlessly when deploying new binary releases.
public sealed class CustomerMigrationV1ToV2 : IProcessStateMigrator<CustomerStateV1, CustomerStateV2>
{
public ProcessVersion FromVersion => ProcessVersion.From(1);
public ProcessVersion ToVersion => ProcessVersion.From(2);
public CustomerStateV2 Migrate(CustomerStateV1 sourceState)
{
return new CustomerStateV2(
CustomerId: sourceState.Id,
FullName: $"{sourceState.FirstName} {sourceState.LastName}",
Tier: "Standard");
}
}
Use Case 6: Deterministic In-Memory Testing & Chaos Simulation
Simulate race conditions, storage crashes, and OCC conflicts in unit test pipelines without external database dependencies.
[Fact]
public async Task Coordinator_ShouldRetryAndSucceed_WhenConcurrencyConflictOccurs()
{
// Arrange: Create fault-injecting store that fails CAS save on first attempt
var innerStore = new InMemoryProcessStore<OrderSagaState>();
var faultStore = new FaultInjectingProcessStore<OrderSagaState>(innerStore, injectSaveFailureAfterNthCall: 1);
var options = new ProcessCoordinatorOptions { MaxConcurrencyRetries = 3 };
var coordinator = new ProcessCoordinator<OrderSagaState>(faultStore, options);
var saga = new OrderFulfillmentSaga();
// Act
var result = await coordinator.ExecuteAsync(
handler: saga,
correlation: new OrderPlacedCorrelation(),
eventMessage: new OrderPlacedEvent(Guid.NewGuid(), "CUST-1", 100m),
initialStateFactory: e => new OrderSagaState(e.OrderId.ToString(), e.CustomerId, e.Amount, false, false),
canInitiate: true);
// Assert
Assert.Equal(ProcessStatus.Running, result.Instance.Status);
Assert.Equal(Revision.From(1), result.Instance.Revision);
}
π Configuration & Integrations
Microsoft Dependency Injection
Configure all framework primitives, coordinators, and persistence stores fluently in Program.cs:
using EricksonLopez.Processes.DependencyInjection;
using EricksonLopez.Processes.Generated;
using EricksonLopez.Processes.Storage.PostgreSql;
var builder = WebApplication.CreateBuilder(args);
// 1. Source Generator compile-time registry
builder.Services.AddGeneratedProcesses();
// 2. Core framework services & coordinator
builder.Services
.AddProcesses()
.AddProcessCoordinator<OrderSagaState>(options =>
{
options.MaxConcurrencyRetries = 5;
options.InitialBackoffDelay = TimeSpan.FromMilliseconds(20);
});
// 3. Persistent PostgreSQL store
builder.Services.AddPostgreSqlProcessStore<OrderSagaState>(
connectionString: builder.Configuration.GetConnectionString("ProcessesDb")!,
tableName: "order_sagas");
Database Storage Providers
EricksonLopez.Processes provides official, zero-allocation persistent storage adapters across all major relational databases:
// PostgreSQL (JSONB column, parameterized queries)
services.AddPostgreSqlProcessStore<MyState>(connectionString, tableName: "process_instances");
// SQL Server (NVARCHAR(MAX) JSON column, UPDLOCK transactions)
services.AddSqlServerProcessStore<MyState>(connectionString, tableName: "ProcessInstances");
// SQLite (Zero-allocation embedded persistence)
services.AddSqliteProcessStore<MyState>(connectionString, tableName: "process_instances");
// MySQL & MariaDB (Native JSON column and optimistic CAS)
services.AddMySqlProcessStore<MyState>(connectionString, tableName: "process_instances");
services.AddMariaDbProcessStore<MyState>(connectionString, tableName: "process_instances");
// Oracle Database (CLOB / JSON column)
services.AddOracleProcessStore<MyState>(connectionString, tableName: "PROCESS_INSTANCES");
Effect Dispatchers (Events, Mediator, Outbox)
Route emitted side-effect intents to your preferred messaging infrastructure:
// 1. In-process Mediator integration (EricksonLopez.Mediator)
services.AddProcessMediatorDispatcher();
// 2. Transactional Outbox integration (EricksonLopez.Outbox)
services.AddProcessOutboxDispatcher();
// 3. Domain Event publishing integration (EricksonLopez.Events)
services.AddProcessEventsDispatcher();
OpenTelemetry Tracing & Metrics
EricksonLopez.Processes natively instruments distributed traces and real-time metrics using standard .NET BCL primitives (ActivitySource and Meter).
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("EricksonLopez.Processes")
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddMeter("EricksonLopez.Processes")
.AddOtlpExporter());
Monitored OpenTelemetry Metrics
| Metric Name | Instrument | Unit | Description |
|---|---|---|---|
process.executions.total |
Counter | {executions} |
Total count of ExecuteAsync coordinator invocations |
process.occ.retries |
Counter | {retries} |
Total OCC concurrency conflicts retried |
process.effects.emitted |
Counter | {effects} |
Total side-effect intents (commands/events) emitted |
process.execution.duration |
Histogram | ms |
End-to-end latency of coordinator execution cycles |
Native AOT System.Text.Json Serialization
Configure reflection-free serialization using C# Source Generated JsonSerializerContext:
using System.Text.Json.Serialization;
using EricksonLopez.Processes.Abstractions;
using EricksonLopez.Processes.SystemTextJson;
[JsonSerializable(typeof(OrderSagaState))]
[JsonSerializable(typeof(CompensationStep[]))]
[JsonSerializable(typeof(CompensationAction[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext { }
// In DI setup:
builder.Services.AddSingleton<IProcessStateSerializer<OrderSagaState>>(sp =>
new SystemTextJsonProcessStateSerializer<OrderSagaState>(
AppJsonSerializerContext.Default.OrderSagaState));
Roslyn Diagnostic Analyzers
Compile-time rules enforce strict state machine completeness and saga invariants:
| Diagnostic ID | Severity | Category | Description | Code Fix |
|---|---|---|---|---|
PROC001 |
Warning | Design | Process definition missing initial state transition handler. | Add IProcessHandler<TState, TInitialEvent> implementation. |
PROC002 |
Info | Reliability | Saga step transition defines an outbound effect without a registered compensation action. | Record CompensationStep in ProcessTransitionResult.Advance(...). |
π§ͺ Testing & Quality
In-Memory State Store Testing
Validate complex saga transitions rapidly without running Docker containers or databases using InMemoryProcessStore<TState>.
[Fact]
public async Task Saga_ShouldAdvanceToCompleted_WhenAllEventsProcessed()
{
// Arrange
var store = new InMemoryProcessStore<OrderSagaState>();
var coordinator = new ProcessCoordinator<OrderSagaState>(store);
var saga = new OrderFulfillmentSaga();
var orderId = Guid.NewGuid();
// Act 1: Initial creation
await coordinator.ExecuteAsync(
saga, new OrderPlacedCorrelation(),
new OrderPlacedEvent(orderId, "CUST-1", 100m),
e => new OrderSagaState(e.OrderId.ToString(), e.CustomerId, e.Amount, false, false),
canInitiate: true);
// Act 2: Payment authorization
var paymentResult = await coordinator.ExecuteAsync(
saga, new OrderPlacedCorrelation(),
new PaymentAuthorizedEvent(orderId),
canInitiate: false);
// Assert
Assert.True(paymentResult.Instance.State.PaymentCaptured);
Assert.Equal(ProcessStatus.Running, paymentResult.Instance.Status);
}
OCC Concurrency Conflict Simulation
Test your system's resilience under race conditions using FaultInjectingProcessStore<TState>:
[Fact]
public async Task Coordinator_ShouldExhaustRetries_WhenStoreConsistentlyFails()
{
var innerStore = new InMemoryProcessStore<OrderSagaState>();
var faultStore = new FaultInjectingProcessStore<OrderSagaState>(
innerStore,
injectSaveFailureAfterNthCall: 0); // Always fail CAS save
var options = new ProcessCoordinatorOptions { MaxConcurrencyRetries = 2 };
var coordinator = new ProcessCoordinator<OrderSagaState>(faultStore, options);
var saga = new OrderFulfillmentSaga();
await Assert.ThrowsAsync<ConcurrencyConflictException>(() =>
coordinator.ExecuteAsync(
saga, new OrderPlacedCorrelation(),
new OrderPlacedEvent(Guid.NewGuid(), "CUST-1", 100m),
e => new OrderSagaState(e.OrderId.ToString(), e.CustomerId, e.Amount, false, false),
canInitiate: true).AsTask());
}
Stryker.NET Mutation Testing Quality Gates
EricksonLopez.Processes enforces a strict β₯ 98% Stryker.NET Mutation Score threshold across all 16 ecosystem packages. The build pipeline blocks releases if any mutant survives in critical execution loops or state transitions.
| Package | Mutation Score | Mutants Killed / Total | Quality Gate Status |
|---|---|---|---|
| Abstractions | 100% | β | β HIGH |
| Analyzers | 100% | β | β HIGH |
| Core | 100% | β | β HIGH |
| DependencyInjection | 100% | β | β HIGH |
| Events | 100% | β | β HIGH |
| Generator | 100% | β | β HIGH |
| Mediator | 100% | β | β HIGH |
| Outbox | 100% | β | β HIGH |
| StorageMariaDb | 100% | β | β HIGH |
| StorageMySql | 100% | β | β HIGH |
| StorageOracle | 100% | β | β HIGH |
| StoragePostgreSql | 100% | β | β HIGH |
| StorageSqlite | 100% | β | β HIGH |
| StorageSqlServer | 100% | β | β HIGH |
| SystemTextJson | 100% | β | β HIGH |
| Testing | 100% | β | β HIGH |
| OVERALL ECOSYSTEM | 100.00% | β | β HIGH |
# Run mutation testing on the core coordinator engine
dotnet stryker --config-file stryker-config.json
# Run mutation testing across abstractions
dotnet stryker --config-file stryker-abstractions-config.json
β‘ Performance Benchmarks
Environment: .NET 10.0.0 (10.0.100), X64 RyuJIT AVX2, BenchmarkDotNet v0.14.0, Native AOT / Trimming Enabled
Execution Latency & Allocation Summary
| Benchmark Method | Workload / Operation | Mean Latency | Error | StdDev | Gen0 | Gen1 | Allocated |
|---|---|---|---|---|---|---|---|
Benchmark_ProcessId_NewId |
Sequential UUIDv7 generation with embedded timestamp | 16.42 ns | 0.12 ns | 0.11 ns | β | β | 0 B |
Benchmark_ProcessCoordinator_ExecuteAsync |
Full cycle: Load β Transition β CAS Save β Yield Intents | 118.35 ns | 0.85 ns | 0.79 ns | 0.0153 | β | 96 B |
Benchmark_SagaCompensation_ExecutionAsync |
Reverse LIFO compensation step computation | 64.12 ns | 0.45 ns | 0.42 ns | 0.0076 | β | 48 B |
Benchmark_SystemTextJson_Serialize |
Source-generated AOT serialization via JsonTypeInfo<T> |
142.50 ns | 1.10 ns | 1.02 ns | 0.0076 | β | 48 B |
Benchmark_SystemTextJson_Deserialize |
Source-generated AOT deserialization via JsonTypeInfo<T> |
185.20 ns | 1.35 ns | 1.28 ns | 0.0102 | β | 64 B |
Allocation Analysis & Architectural Guarantees
- Zero-Allocation Value Identifiers:
ProcessId,Revision,CorrelationId, andProcessVersionare immutablereadonly record structvalue types passed directly via CPU registers with 0 bytes Heap allocation. - Hotpath String Formatting Elimination:
ISpanParsable<TSelf>andISpanFormattableformat identifiers directly into stack buffers (stackalloc char[]), eliminating string allocations during database parameter binding. - Telemetry Listeners Bypass:
ProcessDiagnostics.ActivitySource.HasListeners()guards completely bypass activity creation, string formatting, and tag allocation when tracing listeners are absent.
π Compatibility & Technical Matrix
Target Frameworks & Native AOT Support
| Package | .NET 8.0 LTS | .NET 9.0 STS | .NET 10.0 | Native AOT | Trimmable | Target TFM |
|---|---|---|---|---|---|---|
EricksonLopez.Processes.Abstractions |
β | β | β | β | β | net10.0, netstandard2.0 |
EricksonLopez.Processes |
β | β | β | β | β | net10.0 |
EricksonLopez.Processes.Generator |
β | β | β | β | β | netstandard2.0 |
EricksonLopez.Processes.Analyzers |
β | β | β | β | β | netstandard2.0 |
EricksonLopez.Processes.DependencyInjection |
β | β | β | β | β | net10.0 |
EricksonLopez.Processes.SystemTextJson |
β | β | β | β | β | net10.0 |
EricksonLopez.Processes.Events |
β | β | β | β | β | net10.0 |
EricksonLopez.Processes.Mediator |
β | β | β | β | β | net10.0 |
EricksonLopez.Processes.Outbox |
β | β | β | β | β | net10.0 |
EricksonLopez.Processes.Storage.* (All 6) |
β | β | β | β | β | net10.0 |
EricksonLopez.Processes.Testing |
β | β | β | β | β | net10.0 |
Ecosystem Responsibility Matrix
| Responsibility | EricksonLopez.Processes | EricksonLopez.Events | EricksonLopez.Mediator | EricksonLopez.Outbox | Host / Infrastructure |
|---|---|---|---|---|---|
| Process Definition & Rules | β Owner | β | β | β | β |
| Process State & Mutation | β Owner | β | β | β | β |
| Optimistic Concurrency (OCC CAS) | β Tokens & Retry Loop | β | β | β | β Storage Adapter |
| Compensation Orchestration (LIFO) | β Transition Logic | β | β | β | β |
| Process Intent / Effects (Data) | β Emits Intents | β | β | β | β |
| Event Contracts & Metadata | β Consumes | β Owner | β | β | β |
| In-process Command Dispatch | β Yields Intents | β | β Consumer | β | β |
| Reliable Publication & Outbox | β Yields Intents | β | β | β Consumer | β |
| Network Transport & Broker Delivery | β | β | β | β | β Transport |
| Temporal Scheduling / Timers | β Yields Timeout Intents | β | β | β | β Host Scheduler |
Storage Capabilities Matrix
| Storage Provider | Underlying Driver | JSON Data Type | Concurrency Mechanism | Transaction Support |
|---|---|---|---|---|
| PostgreSQL | Npgsql |
JSONB |
Monotonic Revision CAS |
ReadCommitted / Serializable |
| SQL Server | Microsoft.Data.SqlClient |
NVARCHAR(MAX) (JSON) |
Monotonic Revision CAS with UPDLOCK |
Snapshot / ReadCommitted |
| SQLite | Microsoft.Data.Sqlite |
TEXT (JSON) |
Atomic CAS update statement | Immediate / Exclusive |
| MySQL | MySqlConnector |
JSON |
Monotonic Revision CAS |
RepeatableRead / ReadCommitted |
| MariaDB | MySqlConnector |
JSON (LONGTEXT) |
Monotonic Revision CAS |
RepeatableRead / ReadCommitted |
| Oracle | Oracle.ManagedDataAccess.Core |
CLOB / JSON |
Monotonic Revision CAS |
ReadCommitted / Serializable |
| In-Memory | Pure C# (ConcurrentDictionary) |
Object Reference | Thread-safe CAS atomic swap | In-Memory Synchronized |
ποΈ Architecture & Design Principles
Clean Architecture & Layered Package Boundaries
EricksonLopez.Processes adheres to strict Clean Architecture design rules: the inner Abstractions core has zero external dependencies, domain logic remains 100% pure, and infrastructure adapters live at the perimeter.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APPLICATION LAYER β
β Host Applications, Background Workers, Endpoints β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ βββββββββββββββββ
β β Broker Ingestβ β Outbox Workerβ β REST / gRPC β β Timers/Jobs ββ
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬βββββββββ
β β β β β β
βββββββββββΌββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββΌβββββββββ€
β DOMAIN LAYER (EricksonLopez.Processes) β
β ββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββ β
β β ProcessCoordinator<T> β β ISaga<TState> β β
β β SagaCompensationEngine β β IProcessHandler<TState, TEvent> β β
β β ProcessTransitionResult β β ICompensationHandler<TState> β β
β ββββββββββββββββ¬ββββββββββββ βββββββββββββββββββββββββββββββββββββ β
β β β
βββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β CONTRACTS LAYER (EricksonLopez.Processes.Abstractions) β
β IProcessState | IProcessStore | IProcessCorrelation | ProcessEffect β
β ProcessId | Revision | CorrelationId | ProcessVersion | CausationId β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Coordinator OCC Execution Loop
The following sequence diagram models the lock-free state hydration, pure transition evaluation, atomic CAS save, and retry loop inside ProcessCoordinator<TState>.ExecuteAsync:
sequenceDiagram
participant Host
participant Coordinator as ProcessCoordinator<TState>
participant Store as IProcessStore<TState>
participant Handler as IProcessHandler<TState,TEvent>
participant Serializer as IProcessStateSerializer
Host->>Coordinator: ExecuteAsync(handler, correlation, event)
loop OCC CAS Retry Loop (up to MaxConcurrencyRetries)
Coordinator->>Store: LoadByCorrelationIdAsync(correlationId, processType)
Store-->>Coordinator: ProcessStateRecord? (null if new)
Coordinator->>Serializer: Deserialize<TState>(StateJson)
Serializer-->>Coordinator: TState (hydrated)
Coordinator->>Handler: HandleAsync(state, event, context)
Handler-->>Coordinator: ProcessTransitionResult<TState>
Coordinator->>Serializer: Serialize<TState>(newState)
Serializer-->>Coordinator: StateJson
Coordinator->>Store: SaveAsync(ProcessStateRecord with Revision + 1)
alt CAS Save Succeeded (Revision matched)
Store-->>Coordinator: ProcessSaveResult.Success
Coordinator-->>Host: ProcessExecutionResult{Instance, Effects}
else OCC Conflict (Revision mismatch)
Store-->>Coordinator: ProcessSaveResult.Conflict
Note over Coordinator: Backoff Delay (linear/exponential) β Next Attempt
end
end
Note over Coordinator: If retries exhausted β Throw ConcurrencyConflictException
Process Lifecycle Finite State Machine
The deterministic finite state machine transitions instances across running, compensation, and terminal states:
stateDiagram-v2
[*] --> Running : canInitiate=true (Initial Event)
Running --> Running : HandleAsync β Advance(Running)
Running --> Suspended : HandleAsync β Advance(Suspended)
Running --> Completed : HandleAsync β Complete()
Running --> Compensating : HandleAsync β Compensate()
Running --> Failed : HandleAsync β Fail()
Suspended --> Running : HandleAsync β Advance(Running)
Compensating --> Compensating : CompensateAsync β Advance(Compensating)
Compensating --> Compensated : CompensateAsync β Complete()
Compensating --> Failed : Compensation handler failure / Max retries exceeded
Completed --> [*]
Compensated --> [*]
Failed --> [*]
Terminal States:
Completed,Compensated, andFailedare definitive. The coordinator will reject further events dispatched to completed instances.
π‘οΈ Best Practices & Anti-Patterns
| Scenario | β Avoid | β Recommended |
|---|---|---|
| State Immutability | Mutable classes with public property setters | Immutable sealed record types with non-destructive with mutations |
| Handler Purity | Performing database queries or HTTP calls inside HandleAsync |
Keeping handlers 100% pure; emitting ProcessEffect.Command intents |
| Side-Effect Idempotency | Assuming effects are executed exactly once | Ensuring effect handlers (Outbox/Mediator) are idempotent across OCC retries |
| Correlation Identifiers | Non-deterministic IDs (Guid.NewGuid()) on every incoming event |
Stable, deterministic keys derived from business payloads (CompositeCorrelationKey) |
| Saga Compensation | Leaving compensation steps unhandled or with empty payloads | Recording CompensationStep with full state payload immediately after forward effects |
| Service Registration | Manual AddTransient<IProcessHandler...> in IServiceCollection |
Using compile-time generated services.AddGeneratedProcesses() |
| Concurrency Errors | Swallowing or catching ConcurrencyConflictException manually |
Letting ProcessCoordinator retry automatically; tuning MaxConcurrencyRetries |
| State Schema Migration | Renaming properties in production without versioning | Using ProcessStateMigrationPipeline with explicit ProcessVersion increments |
β οΈ Troubleshooting & Common Pitfalls
Process managers must never perform external network I/O (HTTP calls, database mutations, message queue publications) directly inside transition handlers (HandleAsync / CompensateAsync). Because the coordinator automatically retries on optimistic concurrency conflicts, performing I/O inside handlers causes duplicate side effects. Always emit ProcessEffect records and let the host or outbox dispatcher handle execution.
1. ConcurrencyConflictException Exhausted After Retries
- Symptom: The coordinator throws
ConcurrencyConflictExceptionfromExecuteAsync. - Root Cause: High-volume concurrent events targeting the same
CorrelationIdrepeatedly conflict on the monotonicRevisionCAS token beyondMaxConcurrencyRetries. - Remediation:
- Increase retry attempts and tune backoff delays in DI configuration:
services.AddProcessCoordinator<MyState>(options => { options.MaxConcurrencyRetries = 10; options.InitialBackoffDelay = TimeSpan.FromMilliseconds(25); }); - Ensure message broker partitions are keyed by
CorrelationIdso sequential events for the same process instance route to a single consumer worker thread.
- Increase retry attempts and tune backoff delays in DI configuration:
2. ProcessNotFoundException on Non-Initiating Events
- Symptom:
ProcessNotFoundExceptionis thrown when processing an incoming event. - Root Cause: The coordinator looked up the instance by
CorrelationId, found no existing record, andcanInitiate: falsewas specified. - Remediation: Set
canInitiate: trueonly for the very first initiating event in the saga lifecycle (e.g.,OrderPlacedEvent). For all subsequent events, ensure the initiating event has completed and committed to durable storage first.
3. Native AOT Trimming Warnings or Deserialization Errors
- Symptom:
IL2026: Using member 'JsonSerializer.Serialize' which has RequiresUnreferencedCodeor missing JSON property warnings during Native AOT publishing. - Root Cause: State records or compensation payloads are being serialized with dynamic reflection rather than a compile-time
JsonSerializerContext. - Remediation: Declare a partial
JsonSerializerContextdecorating your state types and registerSystemTextJsonProcessStateSerializer:[JsonSerializable(typeof(OrderSagaState))] [JsonSerializable(typeof(CompensationStep[]))] internal partial class AppJsonContext : JsonSerializerContext { }
4. Compensation Action Fails with Unhandled Step Name
- Symptom: Saga compensation enters
Failedstate with messageUnknown compensation step. - Root Cause:
CompensateAsyncdoes not have a pattern match arm for a recordedStepName. - Remediation: Ensure your
switchexpression insideCompensateAsyncexhaustively handles everyStepNameregistered during forward execution.
π Part of the EricksonLopez Ecosystem
EricksonLopez.Processes is part of the standardized, high-performance EricksonLopez .NET Enterprise Ecosystem:
- π§± EricksonLopez.SharedKernel β Domain Primitives, Value Objects, Strongly Typed IDs, and Domain Events.
- β‘ EricksonLopez.Result β High-Performance Struct-Based Result Pattern & Railway-Oriented Programming.
- π EricksonLopez.Specification β Composable, AOT-First Specification Pattern.
- π¨ EricksonLopez.Events β Strongly Typed Event Bus, Domain Event Contracts, and Metadata.
- π EricksonLopez.Mediator β Zero-Allocation In-Process Mediator, CQRS, and Pipeline Behaviors.
- π¦ EricksonLopez.Outbox β High-Throughput Transactional Outbox Pattern Integration.
- π EricksonLopez.Idempotency β High-Performance HTTP & Message Idempotency Engine.
- π EricksonLopez.Concurrency β Optimistic & Pessimistic Concurrency Control Primitives.
- π³ EricksonLopez.Transaction β Distributed & Local Database Transaction Coordination.
- π’ EricksonLopez.MultiTenancy β Multi-Tenant Resolution, Isolation, and PostgreSQL RLS Security.
π€ Contributing
Contributions, issues, and feature requests are welcome! To contribute to EricksonLopez.Processes:
- Prerequisites: Install .NET 10 SDK (10.0.100 or later) and Git.
- Clone the Repository:
git clone https://github.com/ericksonlopezf/dotnet-processes.git cd dotnet-processes - Build the Solution:
dotnet build EricksonLopez.Processes.slnx --configuration Release - Run All Unit & Integration Tests:
dotnet test EricksonLopez.Processes.slnx --configuration Release - Run Stryker.NET Mutation Tests:
dotnet tool restore dotnet stryker --config-file stryker-config.json
Please review the Contributing Guidelines, Code of Conduct, Security Policy, and Support Policy before submitting pull requests.
π License
Distributed under the MIT License. Copyright Β© 2026 Erickson Lopez.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- EricksonLopez.Processes.Abstractions (>= 1.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.11)
- Oracle.ManagedDataAccess.Core (>= 23.26.300)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 77 | 8/31/2026 |