Architecture
SourceFlow.Net is an event-driven architecture framework implementing CQRS and Event Sourcing patterns. The system separates command processing from event handling, enabling scalable domain-driven design.
Overview
The architecture follows a clean separation between write-side (commands → sagas → entities) and read-side (events → views → view models), connected through an event queue.
- Aggregates encapsulate business logic and send commands
- Command Bus routes commands to appropriate saga handlers
- Sagas handle commands and maintain consistency
- Sagas persist entities to the Entity Store
- Sagas raise events to the Event Queue
- Event Queue dispatches events to subscribers
- Views project events into read models (ViewModels)
- Command Store persists commands for replay
Core Architectural Patterns
| Pattern | Write Side | Read Side |
|---|---|---|
| CQRS | Commands modify state via CommandBus | Queries read from materialised ViewModels |
| Event Sourcing | Commands persisted in CommandStore | Events represent state changes, replay-capable |
| Saga Pattern | Long-running business processes | Coordinate across aggregates |
High-Level Component Diagram
Command Processing Path
- ICommandBus — Entry point for command publishing
- CommandBus — Manages command persistence (assigns sequence number) and dispatching
- ICommandDispatcher — Routes commands to subscribers
- CommandDispatcher — Dispatches to all registered
ICommandSubscriberinstances - CommandSubscriber — Routes commands to appropriate Sagas
- ISaga — Handles commands, persists entities, and produces events
Every command includes SequenceNo for ordering and IsReplay flag for replay scenarios. The metadata is preserved through the entire pipeline.
Event Processing Path
- IEventQueue — Entry point for event publishing (from Sagas)
- EventQueue — Manages event distribution
- IEventDispatcher — Routes events to subscribers
- EventDispatcher — Dispatches to all registered
IEventSubscriberinstances - Aggregate.EventSubscriber — Routes events to Aggregates implementing
ISubscribes<TEvent> - Projections.EventSubscriber — Routes events to Views implementing
IProjectOn<TEvent>
Storage Abstractions
| Layer | Interface | Adapter | Purpose |
|---|---|---|---|
| Commands | ICommandStore | ICommandStoreAdapter | Persist commands with sequence numbers for replay |
| Entities | IEntityStore | IEntityStoreAdapter | Persist aggregate state |
| ViewModels | IViewModelStore | IViewModelStoreAdapter | Persist materialised views for queries |
The adapter pattern provides scoped wrappers around the store interfaces, ensuring proper lifecycle management in DI containers.
Service Lifetimes
| Lifetime | Services |
|---|---|
| Singleton | IEventQueue, IEventDispatcher, IEventSubscriber, IDomainTelemetryService |
| Scoped | ICommandBus, ICommandDispatcher, ICommandSubscriber, ICommandPublisher, Store Adapters |
| Configurable | ISaga, IAggregate, IView implementations (default: Singleton) |
// Configure component lifetime (default: Singleton)
services.UseSourceFlow(ServiceLifetime.Singleton, assemblies);
Built-in Telemetry
The framework includes built-in OpenTelemetry support via IDomainTelemetryService:
| Operation | Trace Name |
|---|---|
| Command dispatch | sourceflow.commandbus.dispatch |
| Command replay | sourceflow.commandbus.replay |
| Command send | sourceflow.commanddispatcher.send |
| Event enqueue | sourceflow.eventqueue.enqueue |
| Event dispatch | sourceflow.eventdispatcher.dispatch |
Tags include command/event type, entity IDs, sequence numbers, and subscriber counts.
Cloud Extension Points
The interface-based design makes cloud extension seamless:
- ICommandDispatcher — New implementation for AWS SQS dispatching
- IEventDispatcher — New implementation for AWS SNS publishing
- Selective routing — Only cloud-configured commands/events go to cloud; others stay local
- Maintained local processing — Existing local processing works alongside cloud dispatch
Design Principles
- Separation of Concerns — Commands, Events, Aggregates, Sagas, and Views are distinct
- Interface-based Design — All major components use interfaces for extensibility
- Dependency Inversion — Components depend on abstractions, not implementations
- Single Responsibility — Each component has a focused purpose
- Open/Closed Principle — Extensible through new implementations without modifying core