Eventuous is a production-grade Event Sourcing library for .NET that provides abstractions and implementations for building event-sourced systems following Domain-Driven Design (DDD) tactical patterns.
- Primary Language: C#
- Target Frameworks: .NET 10, 9, and 8
- License: Apache 2.0
- Copyright: Eventuous HQ OÜ
- Repository: https://github.com/Eventuous/eventuous
- Documentation: https://eventuous.dev
- NuGet Profile: https://www.nuget.org/profiles/Eventuous
eventuous/
├── src/ # Source code organized by component
│ ├── Core/ # Core abstractions and implementations
│ │ ├── src/
│ │ │ ├── Eventuous.Domain/ # Aggregate, State, Events
│ │ │ ├── Eventuous.Persistence/ # Event Store, Aggregate Store
│ │ │ ├── Eventuous.Application/ # Command Services
│ │ │ ├── Eventuous.Subscriptions/ # Event subscriptions
│ │ │ ├── Eventuous.Producers/ # Event producers
│ │ │ ├── Eventuous.Serialization/ # Serialization support
│ │ │ └── Eventuous.Shared/ # Shared utilities
│ │ ├── gen/ # Source generators
│ │ └── test/ # Core tests
│ ├── EventStore/ # EventStoreDB integration
│ ├── Postgres/ # PostgreSQL integration
│ ├── SqlServer/ # SQL Server integration
│ ├── Mongo/ # MongoDB projections
│ ├── RabbitMq/ # RabbitMQ integration
│ ├── Kafka/ # Apache Kafka integration
│ ├── GooglePubSub/ # Google Pub/Sub integration
│ ├── Azure/ # Azure Service Bus integration
│ ├── Redis/ # Redis integration (experimental)
│ ├── Diagnostics/ # OpenTelemetry, Logging
│ ├── Extensions/ # ASP.NET Core extensions
│ ├── Gateway/ # Event-Driven Architecture gateway
│ ├── Testing/ # Testing utilities
│ ├── Experimental/ # Experimental features
│ └── Benchmarks/ # Performance benchmarks
├── test/ # Test helpers and SUTs
├── samples/ # Sample applications
│ ├── esdb/ # EventStoreDB samples
│ └── postgres/ # PostgreSQL samples
├── docs/ # Documentation site (Docusaurus)
└── props/ # Build properties
Location: src/Core/src/Eventuous.Domain/Aggregate.cs
Aggregates are the primary domain model abstraction. They:
- Maintain state through event sourcing
- Enforce business invariants
- Produce domain events when state changes
- Track original and current versions for optimistic concurrency
public abstract class Aggregate<T> where T : State<T>, new()Key methods:
Apply<TEvent>()- Apply event to state and add to changesLoad()- Reconstruct aggregate from event historyEnsureExists()/EnsureDoesntExist()- Guard clauses
Aggregate state is immutable and reconstructed from events using the When() pattern. States implement a functional fold pattern.
Eventuous provides two approaches for handling commands:
Location: src/Core/src/Eventuous.Application/AggregateService/CommandService.cs
Aggregate-based command services work with aggregate instances and orchestrate the aggregate lifecycle:
- Extract aggregate ID from command
- Load aggregate from event store (if existing)
- Execute domain logic on aggregate
- Persist new events
- Return result (success or error)
public abstract class CommandService<TAggregate, TState, TId>Command handlers are registered using fluent API:
On<BookRoom>()
.InState(ExpectedState.New)
.GetId(cmd => new BookingId(cmd.BookingId))
.ActAsync((booking, cmd, _) => booking.BookRoom(...));Location: src/Core/src/Eventuous.Application/FunctionalService/CommandService.cs
Functional command services provide an alternative approach without aggregates, using pure functions:
- Extract stream name from command
- Load events from stream (if existing)
- Restore state from events
- Execute stateless function that produces new events
- Persist new events
- Return result (success or error)
public abstract class CommandService<TState> where TState : State<TState>, new()Command handlers use a fluent API with stream-based operations:
On<BookRoom>()
.InState(ExpectedState.New)
.GetStream(cmd => new StreamName($"Booking-{cmd.BookingId}"))
.Act(cmd => new[] { new RoomBooked(cmd.RoomId, cmd.CheckIn, cmd.CheckOut) });
On<RecordPayment>()
.InState(ExpectedState.Existing)
.GetStream(cmd => new StreamName($"Booking-{cmd.BookingId}"))
.Act((state, events, cmd) => ProducePaymentEvents(state, cmd));Key differences:
- No aggregates: Works directly with state and events
- Pure functions: Handlers are stateless functions that transform state + command → events
- Stream-centric: Operates on streams rather than aggregate instances
- More flexible: Can work with different state types for the same stream
Location: src/Core/src/Eventuous.Persistence/
IEventStore- Combined read/write interfaceIEventReader- Read events from streamsIEventWriter- Append events to streamsIAggregateStore- Legacy aggregate persistence (now deprecated)
Supported implementations:
- EventStoreDB (
Eventuous.EventStore) - PostgreSQL (
Eventuous.Postgresql) - Microsoft SQL Server (
Eventuous.SqlServer)
Location: src/Core/src/Eventuous.Subscriptions/
Subscriptions provide real-time event processing through:
- Event handlers (
IEventHandler) - Consume filters and pipes
- Checkpoint management
- Partitioning support
Event handlers implement:
public interface IEventHandler {
string DiagnosticName { get; }
ValueTask<EventHandlingStatus> HandleEvent(IMessageConsumeContext context);
}Location: src/Core/src/Eventuous.Producers/
Event producers publish events to external systems:
- EventStoreDB
- RabbitMQ
- Apache Kafka
- Google Pub/Sub
- Azure Service Bus
Location: src/Gateway/src/Eventuous.Gateway/
Connects subscriptions with producers for event-driven architectures, enabling cross-bounded-context integration.
| Package | Purpose |
|---|---|
Eventuous |
Umbrella package with core components |
Eventuous.Domain |
Domain model abstractions |
Eventuous.Persistence |
Event store abstractions |
Eventuous.Application |
Command services |
Eventuous.Subscriptions |
Event subscriptions |
Eventuous.Producers |
Event producers |
Eventuous.EventStore |
EventStoreDB support |
Eventuous.Postgresql |
PostgreSQL support |
Eventuous.SqlServer |
SQL Server support |
Eventuous.RabbitMq |
RabbitMQ integration |
Eventuous.Kafka |
Kafka integration |
Eventuous.GooglePubSub |
Google Pub/Sub integration |
Eventuous.Projections.MongoDB |
MongoDB projections |
Eventuous.Diagnostics.OpenTelemetry |
OpenTelemetry support |
Eventuous.Extensions.AspNetCore |
ASP.NET Core integration |
- Follow
.editorconfigsettings in the repository - Use C# nullable reference types
- Prefer immutability where possible
- Follow DDD tactical patterns
- Unit tests in
src/*/test/directories - Integration tests require infrastructure (EventStoreDB, PostgreSQL, etc.)
- Test helpers available in
test/Eventuous.TestHelpers/ - Uses TUnit testing framework (see
test/Eventuous.TestHelpers.TUnit/)
From CONTRIBUTING.md:
- Open an issue before large contributions
- Keep PRs focused on single issues
- Respect existing code formatting
- Only contribute your own work
- Be respectful in discussions
- Solution file:
Eventuous.slnx(new Visual Studio format) - Uses
Directory.Packages.propsfor centralized package management - Docker Compose available for infrastructure:
docker-compose.yml
- Aggregates are reconstructed from event streams
- All state changes produce domain events
- Events are immutable and append-only
- Optimistic concurrency via version checks
- Commands handled by command services
- Queries via read models (projections)
- Subscriptions update read models asynchronously
- Aggregates as consistency boundaries
- Value objects for domain concepts
- Domain events for state changes
- Repository pattern via event stores
Location: src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/
Built-in tracing and metrics for:
- Command handling
- Event persistence
- Subscription processing
- Producer operations
Location: src/Diagnostics/src/Eventuous.Diagnostics.Logging/
Integrates with ASP.NET Core logging infrastructure.
- Define state class inheriting from
State<T> - Define domain events
- Implement
When()methods for event application - Create aggregate class inheriting from
Aggregate<TState> - Add domain methods that call
Apply()
- Create service inheriting
CommandService<TAggregate, TState, TId> - Register command handlers in constructor using
On<TCommand>() - Configure expected state and ID extraction
- Implement business logic in
Act()orActAsync()
- Create service inheriting
CommandService<TState> - Register command handlers in constructor using
On<TCommand>() - Configure expected state (New/Existing/Any) and stream name extraction
- Implement pure functions that return
IEnumerable<object>(events) - For new streams: handler receives only the command
- For existing streams: handler receives state, original events, and command
- Inherit from
EventHandleror implementIEventHandler - Register typed handlers using
On<TEvent>() - Add handler to subscription via
AddHandler()
- Choose subscription provider (EventStoreDB, PostgreSQL, RabbitMQ, etc.)
- Configure subscription with checkpoint storage
- Add event handlers
- Register in DI container
- Start subscription service
- Solution:
Eventuous.slnx - Projects:
Eventuous.[Component].csproj - Interfaces:
I[Name].cs - Abstract classes: Descriptive names (e.g.,
Aggregate.cs,State.cs) - Tests:
Eventuous.Tests.[Component].csproj
-
Breaking Changes: This library is under active development and doesn't follow semantic versioning strictly. Minor versions may introduce breaking changes.
-
Obsolete APIs:
IAggregateStoreis deprecated. UseIEventReader.LoadAggregate<>()andIEventWriter.StoreAggregate<>()extensions instead. -
Stream Names: Aggregates map to event streams using
StreamNameandStreamNameMap. Default pattern:{AggregateType}-{AggregateId}. -
Type Mapping: Events require registration in
TypeMapfor serialization/deserialization. -
Dependency Injection: Extensive DI extensions available in
Eventuous.Extensions.DependencyInjection. -
Async by Default: All I/O operations are async. Use
.NoContext()extension forConfigureAwait(false). -
Diagnostics: Built-in support for EventSource, OpenTelemetry, and ASP.NET Core logging.
-
Testing: Test infrastructure available for aggregate testing, command service testing, and subscription testing. TUnit is the default testing framework. Use async TUnit assertions in tests.
- Main Documentation: https://eventuous.dev
- Blog: https://blog.eventuous.dev
- Discord: Eventuous Server
- YouTube: https://www.youtube.com/@eventuous
- Sample Project: available in
samples/directory - Support: https://ubiquitous.no
For development and production support, contact Ubiquitous.
For sponsorship: https://github.com/sponsors/Eventuous