Skip to content

Commit 429b82a

Browse files
authored
Merge branch 'develop-v1' into release-notes
2 parents 118c9b9 + c2221a4 commit 429b82a

File tree

108 files changed

+1758
-770
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+1758
-770
lines changed

.github/copilot-instructions.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copilot Instructions for EventFlow
2+
3+
These guidelines govern contributions within the EventFlow code base hosted at https://github.com/eventflow/EventFlow/. Follow them whenever collaborating in this repository to stay aligned with the project’s expectations.
4+
5+
## Architecture snapshot
6+
- EventFlow is a CQRS+ES framework; the core runtime lives in `Source/EventFlow` and exposes aggregates, commands, queries, read stores, sagas, jobs, and snapshots.
7+
- Command flow: clients call `CommandBus` (`Source/EventFlow/CommandBus.cs`) which resolves handlers, invokes aggregates deriving from `AggregateRoot<TAggregate, TIdentity>`, and emits events that pipe through subscribers and read-store dispatchers.
8+
- Aggregates load and persist via `IAggregateStore`/`IEventStore`; defaults use the in-memory persistence registered in `EventFlowOptions`, while integration packages under `Source/EventFlow.*` swap in specific stores.
9+
- Read models implement `IReadModel` plus `IAmReadModelFor<...>`; dispatch logic sits in `ReadStores` and uses metadata to map events to view updates.
10+
- Sagas and jobs live under `Source/EventFlow/Sagas` and `Source/EventFlow/Jobs`, coordinating cross-aggregate workflows and deferred execution.
11+
- Documentation that explains the concepts is checked in under `Documentation/`; updates should travel with code changes.
12+
13+
## Extension & configuration guide
14+
- Dependency injection starts with `services.AddEventFlow(o => { ... })` (`Source/EventFlow/Extensions/ServiceCollectionExtensions.cs`); chain option methods to register events, commands, read models, snapshots, sagas, and custom services.
15+
- Use the fluent helpers in `EventFlowOptions` (`Source/EventFlow/EventFlowOptions.cs`) such as `.AddEvents`, `.AddCommands`, `.UseInMemoryReadStoreFor<TReadModel>()`, `.ConfigureOptimisticConcurrencyRetry(...)`, or `.UseEventPersistence<T>()` to pivot storage/backends.
16+
- Strongly typed IDs must derive from `Identity<T>` (`Source/EventFlow/Core/Identity.cs`); create new IDs via `ExampleId.New` or `Identity<T>.With(Guid)` to honor prefix validation.
17+
- When adding domain objects, follow the naming pattern `ThingyAggregate` + `ThingyId` + `ThingyEvent`; see `EventFlow.TestHelpers/Aggregates/Thingy*` for canonical examples including event emit/apply patterns.
18+
- Integration modules (MongoDB, MsSql, PostgreSql, Redis, SQLite, etc.) expose option extensions in their `Extensions/` folder; replicate those patterns when introducing new infrastructure.
19+
- Prefer using `EventFlow.TestHelpers` base classes and fixtures when authoring tests so categories, logging, and deterministic IDs behave consistently.
20+
21+
## Build, test, and verification
22+
- The solution is organized under `EventFlow.sln`; build with `dotnet build EventFlow.sln` (warnings are treated as errors via `Source/Directory.Build.props`).
23+
- Unit tests target `netcoreapp3.1`, `net6.0`, and `net8.0`; run fast feedback with `dotnet test EventFlow.sln --filter "Category!=integration"` and rely on `EventFlow.TestHelpers.Categories` constants when tagging new suites.
24+
- Integration tests span external services (MongoDB, PostgreSQL, SQL Server, RabbitMQ, Elasticsearch, EventStore); start containers with `docker-compose up` before executing the corresponding `*.Tests` projects or include the `integration` category filter.
25+
- Source generators and analyzers live in `Source/EventFlow.SourceGenerators` and `Source/EventFlow.CodeStyle`; ensure the .NET SDK version supports C# 12 and keep analyzer warnings clean.
26+
- Documentation builds use MkDocs (`requirements.txt`); run `pip install -r requirements.txt` followed by `mkdocs serve` when verifying doc updates.
27+
28+
## Coding conventions & review tips
29+
- Favor async APIs and accept `CancellationToken` parameters throughout—the core dispatchers expect cooperative cancellation (see `CommandBus.PublishAsync` and `AggregateStore` methods).
30+
- New events should inherit `AggregateEvent<TAggregate, TIdentity>`, carry immutable data, and rely on aggregate `Apply` methods to mutate state; never mutate state directly inside command handlers.
31+
- Subscribers and read stores should request dependencies via constructor injection and avoid static singletons; look at `Source/EventFlow/Subscribers` for the expected interface contracts.
32+
- When wiring new persistence, register required DI services before calling `.UseEventPersistence<T>()` to avoid the `RemoveAll<IEventPersistence>()` guard removing your registration.
33+
- Keep public APIs binary compatible where possible; breaking changes require updates in `Documentation/` and `RELEASE_NOTES.md`.
34+
- Mirror existing namespace layout (`EventFlow.{Feature}`) and group files into folders matching their conceptual role to keep source generators and discovery heuristics effective.
35+
36+
## Operational safeguards
37+
- Avoid invoking GitHub management tools that mutate remote state (issues, pull requests, repositories, projects, workflows, labels, security alerts, notifications, etc.) unless the user has granted explicit permission in the current conversation.
38+
- Never run mutating `git` commands (commit, push, merge, rebase, reset, clean, etc.) without explicit user authorization; limit `git` usage to read-only inspection by default.
39+
- If permission is unclear, pause and ask the user before attempting any action that could alter repository or GitHub state.

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,6 @@ FakesAssemblies/
201201
# Git commit history
202202
/-la
203203

204-
# Visual Studio Code
205-
/.ionide
206-
/.vscode
207-
208204
#####################################################
209205

210206
# Project specific files

.vscode/mcp.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"servers": {
3+
"github-official": {
4+
"url": "https://api.githubcopilot.com/mcp/",
5+
"type": "http"
6+
}
7+
},
8+
"inputs": []
9+
}
Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,146 @@
1-
---
21
title: Configuration
32
---
43

5-
# Configuration
4+
# EventFlow runtime configuration
5+
6+
EventFlow ships with sensible defaults, but most production workloads need to tune how the pipeline reacts to retries, subscriber failures, and replay throughput. All of those switches are exposed via `EventFlowOptions` when you wire the framework into your dependency injection container.
7+
8+
## How configuration is applied
9+
10+
Calling `AddEventFlow` registers the core services and gives you an `IEventFlowOptions` hook. Every configuration tweak happens inside that callback.
11+
12+
```csharp
13+
using System;
14+
using Microsoft.Extensions.DependencyInjection;
15+
using EventFlow;
16+
17+
var services = new ServiceCollection();
18+
19+
services.AddEventFlow(options =>
20+
{
21+
options
22+
.ConfigureOptimisticConcurrencyRetry(retries: 6, delayBeforeRetry: TimeSpan.FromMilliseconds(250))
23+
.Configure(cfg =>
24+
{
25+
cfg.ThrowSubscriberExceptions = true;
26+
cfg.IsAsynchronousSubscribersEnabled = true;
27+
});
28+
});
29+
30+
using var provider = services.BuildServiceProvider();
31+
```
32+
33+
Under the covers `AddEventFlow` calls `EventFlowOptions.New(serviceCollection)` and stores a single `EventFlowConfiguration` instance in the container as both `IEventFlowConfiguration` and `ICancellationConfiguration`. Additional fluent helpers (e.g., `.AddEvents`, `.AddCommands`, `.UsePostgreSqlEventStore`) can be chained in the same callback.
34+
35+
!!! tip
36+
You can invoke `.Configure(...)` multiple times. Each delegate receives the same `EventFlowConfiguration` instance, so later calls simply overwrite earlier values.
637

7-
EventFlow configuration can be done via the `.Configure(o => {})` method, which is available on the `EventFlowOptions` object.
38+
## `EventFlowConfiguration` reference
39+
40+
`EventFlowConfiguration` is defined in `Source/EventFlow/Configuration/EventFlowConfiguration.cs`. All properties are mutable so that they can be adjusted during startup.
41+
42+
| Setting | Default | Used by | Effect |
43+
| --- | --- | --- | --- |
44+
| `LoadReadModelEventPageSize` | `200` | `ReadModelPopulator.LoadEventsAsync` | Controls how many events are fetched per call when bulk-populating read models via `IReadModelPopulator`. Increase this if your event store can stream large pages efficiently; reduce it when replaying against constrained backends.
45+
| `PopulateReadModelEventPageSize` | `10000` | `ReadModelPopulator.ProcessEventQueueAsync` | Sets the batch size used when dispatching replayed events to read-store managers. Lower values trade throughput for lower memory pressure during large replays.
46+
| `NumberOfRetriesOnOptimisticConcurrencyExceptions` | `4` | `OptimisticConcurrencyRetryStrategy` | Upper bound on how many times the aggregate store retries commits when the persistence layer reports `OptimisticConcurrencyException`.
47+
| `DelayBeforeRetryOnOptimisticConcurrencyExceptions` | `00:00:00.100` | `OptimisticConcurrencyRetryStrategy` | Delay inserted between those retries. Combine with the retry count to soften hot spots in high-contention aggregates.
48+
| `ThrowSubscriberExceptions` | `false` | `DispatchToEventSubscribers` | When `false`, synchronous subscriber exceptions are logged and wrapped in a resilience strategy; when `true`, they are rethrown so the calling command handler observes the failure immediately.
49+
| `IsAsynchronousSubscribersEnabled` | `false` | `DomainEventPublisher.PublishToAsynchronousSubscribersAsync` | When enabled, every asynchronous subscriber invocation is scheduled through `IJobScheduler` (`InstantJobScheduler` by default). Pair this with a durable scheduler such as `EventFlow.Hangfire` to honor delayed execution.
50+
| `CancellationBoundary` | `CancellationBoundary.BeforeCommittingEvents` | `ICancellationConfiguration.Limit` | Decides how far cancellation tokens propagate through the command pipeline. Choose a later boundary if downstream infrastructure (read stores, subscribers) should respect cancellation requests.
51+
| `ForwardOptimisticConcurrencyExceptions` | `false` | `AggregateStore` | When `true`, optimistic concurrency exceptions are forwarded to `IAggregateStoreResilienceStrategy.HandleCommitFailedAsync` before bubbling out. Use this if you implement a custom resilience strategy that can translate conflicts into domain-specific outcomes.
52+
53+
!!! note
54+
The enum values for `CancellationBoundary` are defined in `Configuration/Cancellation/CancellationBoundary.cs` and progress in chronological order through the command pipeline (`BeforeUpdatingAggregate``BeforeCommittingEvents``BeforeUpdatingReadStores``BeforeNotifyingSubscribers``CancelAlways`).
55+
56+
## Practical configuration scenarios
57+
58+
### Enable durable asynchronous subscribers
859

960
```csharp
10-
using var serviceCollection = new ServiceCollection()
11-
// ...
12-
.AddEventFlow(e => e.Configure(o =>
61+
using Hangfire;
62+
using EventFlow.Hangfire.Extensions;
63+
64+
services.AddHangfire(config => config.UseInMemoryStorage());
65+
services.AddHangfireServer();
66+
67+
services.AddEventFlow(options =>
68+
{
69+
options.Configure(cfg =>
1370
{
14-
o.IsAsynchronousSubscribersEnabled = true;
15-
o.ThrowSubscriberExceptions = true;
16-
}))
17-
// ...
18-
.BuildServiceProvider();
71+
cfg.IsAsynchronousSubscribersEnabled = true;
72+
});
73+
74+
options.UseHangfireJobScheduler();
75+
});
1976
```
2077

21-
In this example, we enable asynchronous subscribers and configure EventFlow to throw exceptions for subscriber errors. You can customize the configuration options to suit your needs.
78+
Setting `IsAsynchronousSubscribersEnabled` causes `DomainEventPublisher` to enqueue a `DispatchToAsynchronousEventSubscribersJob` for every emitted domain event. Without a scheduler such as Hangfire, the bundled `InstantJobScheduler` executes jobs immediately in-process, effectively making asynchronous subscribers synchronous.
79+
80+
### Harden aggregates against hot-spot contention
81+
82+
```csharp
83+
services.AddEventFlow(options =>
84+
{
85+
options
86+
.ConfigureOptimisticConcurrencyRetry(retries: 8, delayBeforeRetry: TimeSpan.FromMilliseconds(500))
87+
.Configure(cfg => cfg.ForwardOptimisticConcurrencyExceptions = true);
88+
});
89+
```
90+
91+
The retry helper only adjusts the built-in retry strategy. Setting `ForwardOptimisticConcurrencyExceptions` allows a custom `IAggregateStoreResilienceStrategy` to inspect the conflict and, for example, emit a domain-specific execution result instead of throwing.
92+
93+
### Tune read model replay throughput
94+
95+
```csharp
96+
services.AddEventFlow(options =>
97+
{
98+
options.Configure(cfg =>
99+
{
100+
cfg.LoadReadModelEventPageSize = 1000; // event store paging
101+
cfg.PopulateReadModelEventPageSize = 2000; // read model batch size
102+
});
103+
});
104+
```
105+
106+
These knobs directly influence `IReadModelPopulator.PopulateAsync`. Smaller batches reduce memory footprint and can help when replaying to remote databases; larger batches maximize throughput when the event store and read store are co-located.
107+
108+
### Adjust cancellation semantics
109+
110+
```csharp
111+
services.AddEventFlow(options =>
112+
{
113+
options.Configure(cfg =>
114+
{
115+
cfg.CancellationBoundary = CancellationBoundary.BeforeNotifyingSubscribers;
116+
});
117+
});
118+
```
119+
120+
Raising the boundary ensures cancellation tokens are honored while rebuilding read stores, but once the boundary is crossed EventFlow will run to completion to keep the event store and read models consistent.
121+
122+
## Consuming configuration at runtime
123+
124+
Every component registered with the container can request `IEventFlowConfiguration` or `ICancellationConfiguration` to observe these values.
125+
126+
```csharp
127+
using EventFlow.Configuration;
128+
129+
public class ProjectionWorker(IEventFlowConfiguration configuration)
130+
{
131+
public Task HandleAsync(CancellationToken cancellationToken)
132+
{
133+
var maxBatchSize = configuration.PopulateReadModelEventPageSize;
134+
// ... use the configured value
135+
return Task.CompletedTask;
136+
}
137+
}
138+
```
139+
140+
This is useful when custom infrastructure (for example, an outbox publisher) needs to stay in lockstep with the same retry and cancellation semantics as the built-in components.
141+
142+
## See also
143+
144+
- [Subscribers](../basics/subscribers.md) — explains synchronous vs. asynchronous subscribers in detail.
145+
- [Queries and read stores](../basics/queries.md) and [Read store integrations](../integration/read-stores.md) — pair naturally with the read model replay settings.
146+
- [Commands](../basics/commands.md) — outlines how command handlers surface execution results that may be impacted by retry and exception settings.

0 commit comments

Comments
 (0)