This document outlines the phased implementation strategy for extending the Orleans.StateMachine library with event sourcing and advanced features as specified in plan.md.
Author: Michael Ivertowski
License: MIT
Based on: ManagedCode.Orleans.StateMachine (with acknowledgments to original authors)
- Event-First Architecture: Every state transition is an event
- Backward Compatibility: Maintain compatibility with existing StateMachine usage where possible
- Orleans Native: Leverage Orleans patterns (JournaledGrain, Streams, Reminders)
- Type Safety: Strongly-typed APIs with compile-time safety
- Testability: Comprehensive testing including replay testing
- Observability: Built-in telemetry and audit trails
Goal: Establish new project structure and basic event sourcing infrastructure
Tasks:
- Rename namespace from
ManagedCode.Orleans.StateMachinetoOrleans.StateMachineES - Update package metadata (author, version 1.0.0-alpha)
- Create base classes:
EventSourcedStateMachineGrain<TState, TTrigger, TEvent>(extends JournaledGrain)StateTransitionEvent<TState, TTrigger>base event class
- Implement core event sourcing:
- Auto-emit events on transitions
- Event replay on grain activation
- Deduplication keys for idempotency
- Create comprehensive unit tests for event sourcing
Deliverables:
- New NuGet package:
Orleans.StateMachineES - Basic event-sourced state machine working
- Migration guide from original library
Goal: Full JournaledGrain integration with outbox pattern
Tasks:
- Implement transition event types:
public record StateTransitionEvent<TState, TTrigger>( TState FromState, TState ToState, TTrigger Trigger, DateTime Timestamp, string? CorrelationId, string? DedupeKey );
- Add automatic event confirmation:
- Hook into state transitions
- Call
RaiseEventandConfirmEventsautomatically
- Implement outbox pattern:
- Queue events after confirmation
- Publish to Orleans Streams
- Handle failures with retry
- Create EventSourcedStateMachineGrainState for snapshots
- Add configuration options:
.ConfigureEventSourcing(options => { options.AutoConfirmEvents = true; options.PublishToStream = true; options.StreamProvider = "SMS"; })
Testing:
- Event replay tests
- Idempotency tests
- Outbox pattern integration tests
Goal: Add time-based state management
Tasks:
- Implement state timeouts:
.Configure(State.Processing) .WithTimeout(TimeSpan.FromMinutes(5), State.Failed) .WithRetryTimer(TimeSpan.FromSeconds(30))
- Add reminder-based durable timeouts (for >1 minute)
- Add timer-based non-durable retries (for <1 minute)
- Implement temporal guards:
.PermitIf(Trigger.Expire, State.Expired, () => DateTime.UtcNow > expirationDate)
- Create timeout events for audit trail
- Handle timer/reminder cleanup on state exit
Testing:
- Timeout transition tests
- Reminder persistence tests
- Timer cancellation tests
Goal: Support complex state compositions
Tasks:
- Implement hierarchical states:
machine.Configure(State.Operating) .SubstateOf(State.Active) .InitialSubstate(State.Operating.Monitoring);
- Add orthogonal regions:
machine.DefineOrthogonalRegion("EvidenceRetention") .WithStates(RetentionState.Collecting, RetentionState.Archived);
- Ensure deterministic serialization for event sourcing
- Update state inspection APIs for hierarchy
- Implement composite state events
Testing:
- Hierarchical transition tests
- Orthogonal region independence tests
- Serialization determinism tests
Goal: Multi-grain orchestration with compensation
Tasks:
- Create saga definition DSL:
public class InvoiceSaga : StateMachineSaga<InvoiceState> { protected override void Configure() { DefineStep("CreateInvoice") .CallGrain<IInvoiceGrain>(g => g.Create()) .WithCompensation(g => g.Delete()); } }
- Implement correlation ID tracking
- Add compensation execution on failure
- Create saga state persistence
- Implement distributed transaction patterns
Testing:
- Saga completion tests
- Compensation rollback tests
- Distributed failure scenarios
Goal: Support FSM evolution without breaking existing grains
Tasks:
- Implement FSM versioning:
[StateMachineVersion("2.0")] protected override StateMachine<TState, TTrigger> BuildStateMachine()
- Add shadow mode for blue/green testing:
grain.EnableShadowMode("2.0");
- Create migration hooks:
protected override Task MigrateState(string fromVersion, string toVersion)
- Implement version compatibility checks
- Add version telemetry
Testing:
- Version migration tests
- Shadow mode comparison tests
- Backward compatibility tests
Goal: Comprehensive monitoring and compliance
Tasks:
- OpenTelemetry integration:
- Spans for state entry/exit/transition
- Metrics (transition counts, time-in-state)
- Distributed tracing with correlation IDs
- Audit logging:
public record AuditEntry( string Who, DateTime When, string Action, string Reason, Dictionary<string, object> Metadata );
- Add ICFR compliance features
- Create dashboards templates (Grafana/Prometheus)
Testing:
- Telemetry emission tests
- Audit trail completeness tests
- Performance impact tests
Goal: Tools and utilities for development
Tasks:
- State diagram generation:
var mermaid = machine.ToMermaidDiagram(); var plantUml = machine.ToPlantUml();
- Replay testing framework:
var tester = new StateMachineReplayTester<TState, TTrigger>(); tester.Feed(eventStream).AssertState(State.Completed);
- Roslyn source generator for typed triggers:
# statemachine.yaml states: [Initial, Processing, Completed] triggers: [Start, Process, Complete]
- Hot reload support for development
- Visual Studio/Rider extensions for visualization
Testing:
- Diagram generation accuracy
- Replay framework validation
- Source generator output tests
- Each phase includes comprehensive unit tests
- Use xUnit with FluentAssertions
- Mock Orleans runtime where needed
- Test coverage target: >90%
- Use Orleans TestCluster
- Test grain interactions
- Verify event sourcing persistence
- Test timer/reminder integration
- Benchmark state transitions
- Measure event sourcing overhead
- Load test with multiple concurrent grains
- Memory profiling for long-running grains
- Capture production event streams
- Replay against new versions
- Validate state consistency
- Test migration scenarios
- Change namespace imports
- Inherit from
EventSourcedStateMachineGraininstead ofStateMachineGrain - Add event type parameter
- Optional: Enable event sourcing features gradually
// Before
using ManagedCode.Orleans.StateMachine;
public class DoorGrain : StateMachineGrain<DoorState, DoorTrigger>
// After
using Orleans.StateMachineES;
public class DoorGrain : EventSourcedStateMachineGrain<DoorState, DoorTrigger, DoorEvent>- Basic event sourcing
- Namespace migration
- Core functionality
- Timers and reminders
- Hierarchical states
- Production-ready event sourcing
- Full feature set
- Production tested
- Complete documentation
- API Documentation: XML comments on all public APIs
- Tutorials: Step-by-step guides for each feature
- Migration Guide: From original library
- Best Practices: Patterns and anti-patterns
- Sample Applications: Real-world examples
- Adoption: NuGet downloads and GitHub stars
- Performance: <5% overhead vs base Orleans grains
- Reliability: >99.9% state consistency in replay tests
- Developer Satisfaction: Issue resolution time <48h
- Test Coverage: >90% code coverage
- Orleans API Changes: Abstract Orleans dependencies
- Performance Regression: Continuous benchmarking
- Backward Compatibility: Extensive migration tests
- Complexity: Modular design with feature flags
- Maintenance Burden: Automated testing and CI/CD
- Set up new project structure with ivlt namespace
- Implement Phase 1 foundation
- Create initial NuGet package
- Begin Phase 2 event sourcing integration
- Establish CI/CD pipeline