You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: Documentation/additional/faq.md
+62-27Lines changed: 62 additions & 27 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,43 +6,78 @@ title: FAQ
6
6
7
7
## How can I ensure that only specific users can execute commands?
8
8
9
-
You can either replace the implementation of `ICommandBus` with your own implementation, or add a decorator that adds the authentication logic.
9
+
EventFlow deliberately keeps the command pipeline thin. The default `CommandBus.PublishAsync` resolves the single `ICommandHandler<,,,>` that matches a command and forwards the call to `IAggregateStore.UpdateAsync`. No authorization hooks are executed for you.
10
+
11
+
You therefore have to enforce authorization either close to the domain or by decorating the command bus:
12
+
13
+
- Inject any ambient context (for example an `ICurrentUser`) into your command handlers and return a failed `IExecutionResult` or throw a `DomainError` if the caller is not allowed to proceed.
14
+
- Replace the `ICommandBus` registration with a decorator that checks permissions before delegating to the inner bus. EventFlow registers the bus with `TryAddTransient<ICommandBus, CommandBus>()`, so a subsequent `services.Replace(...)` takes over cleanly. One possible decorator looks like this:
This keeps sensitive logic centralized while still letting the built-in `CommandBus` discover handlers and persist aggregates.
10
61
11
62
## Why isn't there a "global sequence number" on domain events?
12
63
13
-
While this is easy to support in some event stores like MSSQL, it
14
-
doesn't really make sense from a domain perspective. Greg Young also has
15
-
this to say on the subject:
64
+
Every `IDomainEvent` emitted by an aggregate exposes the `AggregateSequenceNumber` from `DomainEvent<TAggregate, TIdentity, TAggregateEvent>` and repeats the value in metadata under `MetadataKeys.AggregateSequenceNumber`. EventFlow guarantees ordering inside a single aggregate root and stops there, because cross-aggregate ordering is a projection concern rather than a domain invariant.
16
65
17
-
!!! quote
18
-
Order is only assured per a handler within an aggregate root
19
-
boundary. There is no assurance of order between handlers or between
20
-
aggregates. Trying to provide those things leads to the dark side. >
Most event store integrations (e.g., `MsSqlEventPersistence`, `PostgresSqlEventPersistence`, `SQLiteEventPersistence`) maintain their own `GlobalSequenceNumber` internally so they can serve `IEventStore.LoadAllEventsAsync(...)`. That API returns an `AllEventsPage` with a `GlobalPosition` token you can persist if you are building a log-reading process. Once the events are materialized into `IDomainEvent` instances and dispatched to handlers, the global number is intentionally not exposed—reactive code should not rely on cross-aggregate ordering promises.
23
67
68
+
If you need an application-level notion of global ordering, capture the `GlobalPosition` when you read from `LoadAllEventsAsync` or store it alongside your read model state.
24
69
25
70
## Why doesn't EventFlow have a unit of work concept?
26
71
27
-
Short answer, you shouldn't need it. But Mike has a way better answer:
28
-
29
-
!!! quote
30
-
In the Domain, everything flows in one direction: forward. When
31
-
something bad happens, a correction is applied. The Domain doesn't
32
-
care about the database and UoW is very coupled to the db. In my
33
-
opinion, it's a pattern which is usable only with data access
34
-
objects, and in probably 99% of the cases you won't be needing it.
35
-
As with the Singleton, there are better ways but everything depends
36
-
on proper domain design. > `Mike
72
+
The aggregate itself is the unit of consistency in EventFlow. A command published through the `CommandBus` flows into `IAggregateStore.UpdateAsync`, which (1) rehydrates the aggregate by replaying its event stream, (2) executes your domain logic delegate, (3) commits the newly emitted events in a single call to `IEventPersistence.CommitEventsAsync`, and (4) publishes the resulting domain events to read stores, subscribers, and sagas. Because aggregate state comes entirely from events, there is no ambient change tracker to flush and a classic unit-of-work abstraction would not add any extra safety.
When you really need to coordinate with another resource (e.g., enlist additional SQL statements), plug into `IAggregateStoreResilienceStrategy` or move the extra work into a subscriber that runs after the events have been durably written.
39
75
40
-
If your case falls within the 1% case, write a decorator for the
41
-
`ICommandBus` that starts a transaction, use MSSQL as event store and
42
-
make sure your read models are stored in MSSQL as well.
76
+
## Why are subscribers receiving events out of order?
43
77
78
+
EventFlow publishes events in stages through `DomainEventPublisher`:
44
79
45
-
## Why are subscribers receiving events out of order?
80
+
- Read stores and synchronous subscribers run one event at a time and in order. `DispatchToEventSubscribers.DispatchToSynchronousSubscribersAsync` awaits each handler before moving on.
81
+
- Asynchronous subscribers are intentionally different. Each `IDomainEvent` is wrapped in a `DispatchToAsynchronousEventSubscribersJob` and scheduled via `IJobScheduler`. The default `InstantJobScheduler` executes the jobs immediately, but `DomainEventPublisher` starts them with `Task.WhenAll(...)`, so completion order is not guaranteed and alternative schedulers (such as Hangfire) may execute them on other workers entirely.
46
82
47
-
It might be that your aggregates are emitting multiple events. Read about
48
-
[subscribers and out of order events](../basics/subscribers.md#out-of-order-events).
83
+
If your projection relies on strict ordering, keep it synchronous or persist enough information—such as the `AggregateSequenceNumber`—to detect and discard late arrivals.
0 commit comments