The Standard Events
What this skill is
This skill governs how events are architected, implemented, and tested under The Standard.
It covers the CulDeSac pattern, event brokers, foundation event services, validation, exception mapping, and test structure for publish and subscribe operations.
Explicit coverage map
This skill explicitly covers:
- The CulDeSac pattern and why it exists in Standard-compliant systems
- Event broker structure and implementation
- Foundation event service implementation: main, Validations, and Exceptions files
- Publish and SubscribeTo operation contracts and naming conventions
- Validation rules for null entity and null event handler
- Exception mapping: Validation and Service exceptions only (no Dependency exceptions)
- File and partial-class structure for brokers, services, and tests
- Test structure: Logic, Validations, and Exceptions per operation
- Naming conventions enforced across the entire events layer
- Orchestration integration: how publisher and subscriber orchestration services consume the event service
- Dependency injection registration: lifetime requirements for the event broker and event service
- Startup activation: how and where SubscribeTo calls must be invoked at application startup
When to use
Use this skill whenever implementing, reviewing, expanding, or testing event-driven communication between services.
Use it whenever deciding how to publish a domain event, how to subscribe to one, or how to validate and handle failures in an event service.
Use it when determining whether the CulDeSac pattern is appropriate for a given service.
The CulDeSac pattern
The CulDeSac pattern applies when a service needs to send a domain event outward without expecting a synchronous return from any subscriber.
It is a dead-end in the call graph: data flows in one direction, callers are not blocked waiting for a result, and subscribers are free to react independently.
Why use events
- To decouple services across bounded contexts without creating direct service-to-service dependencies.
- To allow multiple subscribers to react to a single domain event independently.
- To avoid creating orchestration services that must know about every downstream consumer.
- To support fire-and-forget flows where the publisher does not need confirmation from subscribers.
- To enable downstream services to scale and evolve without affecting the publisher.
- To prevent any single orchestrator from accumulating too many responsibilities over time.
Event service doctrine
- Event services are always foundation services.
- Event services sit at the boundary between the domain model and the event infrastructure.
- Event services must not depend on other services -- only on the event broker.
- Event services expose exactly two operations per entity: Publish[Entity]Async and SubscribeTo[Entity]Event.
- Event services do not inject a logging broker -- they have no logging dependency by design.
- Event services use two TryCatch delegates to isolate exception handling from business logic.
- Event services do NOT catch Dependency or CriticalDependency exceptions -- they do not call HTTP or storage APIs.
Broker implementation doctrine
- The event broker is the only infrastructure dependency of an event service.
- The event broker wraps any chosen event infrastructure -- no specific event library is mandated.
- Each entity gets its own event client instance, typed to that entity, declared in the broker constructor.
- The broker is split into four files: base interface, entity interface partial, base implementation, entity implementation partial.
- Broker operations are thin pass-throughs to the underlying event infrastructure -- no logic lives in the broker.
Orchestration integration
The event service is consumed exclusively by orchestration services.
Controllers, processing services, and other foundation services must never depend on an event service directly.
Publisher side
An orchestration service that needs to raise a domain event calls Publish[Entity]Async on the event service.
The orchestration service owns the composition of the entity and delegates the publish call to the event service.
The publisher orchestration service knows nothing about which services will subscribe -- it only knows it must publish.
Subscriber side
An orchestration service that needs to react to a domain event exposes a method named SubscribeTo[Entity]Events (plural).
That method calls this.[entity]EventService.SubscribeTo[Entity]Event(handler) where handler is a private async method.
The handler receives the entity and performs the orchestration reaction -- calling foundation services as needed.
Only the subscriber orchestration service knows what to do with the event; the publisher is unaware of subscribers.
Naming distinction: plural vs singular
- The event service method is always singular:
SubscribeTo[Entity]Event.
- The orchestration service wrapper method is always plural:
SubscribeTo[Entity]Events.
- The plural wrapper exists because the orchestration service may route the event to multiple sub-operations.
- The plural wrapper is the method called at startup -- never the singular event service method directly.
Dependency injection and startup activation
Registration rules
- All orchestration services that publish or subscribe must be registered in DI.
- The required DI lifetime for
IEventBroker and EventBroker depends on the event infrastructure in use.
- For in-memory event infrastructure (such as LeVent): singleton is required for correctness -- client instances hold subscription handler registrations in memory; scoped or transient registrations produce fresh instances with no registered handlers, so events are never delivered.
- For external event infrastructure (such as Azure Service Bus or EventHighway): follow the client library's own lifetime recommendations -- subscriptions live in the external service and survive independently of the client instance, but clients typically require singleton lifetime for connection reuse and to keep message processors alive.
I[Entity]EventService and [Entity]EventService must be registered with a lifetime that matches the broker they depend on -- never register the service with a longer lifetime than the broker.
- When in doubt, prefer singleton -- it is safe for all known event infrastructure and avoids silent subscription failures.
Startup activation rules
- Event subscriptions are not self-activating -- they must be explicitly started at application startup.
- After the DI container is built, every subscribing orchestration service must have its
SubscribeTo[Entity]Events() method called.
- The activation call must appear in
Configure() (Startup.cs style) or its equivalent in a minimal API host setup.
- In Startup.cs: use
app.ApplicationServices.GetService<I[Entity]OrchestrationService>().SubscribeTo[Entity]Events().
- In minimal API (Program.cs): use
app.Services.GetService<I[Entity]OrchestrationService>().SubscribeTo[Entity]Events() after app = builder.Build().
- If the startup activation call is missing, no subscriber will ever receive events -- the subscription is silently never registered.
- Every subscribing orchestration service must have exactly one startup activation call per subscription.
Validation rules for event services
Publish validation
- Validate the entity is not null before publishing.
- A null entity must throw NullEventException immediately (circuit-breaking).
- Null entity validation must not collect further errors -- it breaks immediately.
Subscribe validation
- Validate the event handler delegate is not null before subscribing.
- A null handler must throw NullEventHandlerException immediately (circuit-breaking).
- Null handler validation must not collect further errors -- it breaks immediately.
Exception handling rules
- All validation failures must produce [Entity]EventValidationException wrapping the inner exception.
- All unexpected exceptions must be wrapped in FailedEventServiceException, then in [Entity]EventServiceException.
- FailedEventServiceException must carry the original exception as innerException and its Data collection.
- Event services do not use DependencyValidationException or DependencyException categories.
- TryCatch delegates must be used to separate exception handling from core logic.
Naming conventions
- Interface: I[Entity]EventService.
- Implementation: [Entity]EventService (internal partial class).
- Publish operation: Publish[Entity]Async (returns ValueTask, async).
- Subscribe operation: SubscribeTo[Entity]Event (returns void, synchronous).
- Subscribe must NEVER be named ListenTo[Entity]Event.
- Broker interface: IEventBroker (shared), with entity-specific partials in IEventBroker_[Entity].cs.
- Exception naming: Null[Entity]EventException, Null[Entity]EventHandlerException, Failed[Entity]EventServiceException, [Entity]EventValidationException, [Entity]EventServiceException.
Test rules for event services
- Test SubscribeTo[Entity]Events happy path first.
- Test Publish[Entity]Async happy path second.
- Test validation failures third (null handler, null entity).
- Test service exceptions fourth (unexpected errors in both operations).
- Event service tests must NOT declare a loggingBrokerMock -- there is no logging broker.
- Subscribe handler mocks must use Mock<Func<[Entity], ValueTask>>.
- Always end every test with eventBrokerMock.VerifyNoOtherCalls().
- Use Times.Once for expected calls and Times.Never for calls skipped due to validation failures.
1---2name: the-standard-events3description: Enforces Standard event-driven architecture using the CulDeSac pattern, event brokers, foundation event services, validation, exception handling, and event service testing.4---56# The Standard Events78## What this skill is910This skill governs how events are architected, implemented, and tested under The Standard.11It covers the CulDeSac pattern, event brokers, foundation event services, validation, exception mapping, and test structure for publish and subscribe operations.1213## Explicit coverage map1415This skill explicitly covers:1617- The CulDeSac pattern and why it exists in Standard-compliant systems18- Event broker structure and implementation19- Foundation event service implementation: main, Validations, and Exceptions files20- Publish and SubscribeTo operation contracts and naming conventions21- Validation rules for null entity and null event handler22- Exception mapping: Validation and Service exceptions only (no Dependency exceptions)23- File and partial-class structure for brokers, services, and tests24- Test structure: Logic, Validations, and Exceptions per operation25- Naming conventions enforced across the entire events layer26- Orchestration integration: how publisher and subscriber orchestration services consume the event service27- Dependency injection registration: lifetime requirements for the event broker and event service28- Startup activation: how and where SubscribeTo calls must be invoked at application startup2930## When to use3132Use this skill whenever implementing, reviewing, expanding, or testing event-driven communication between services.33Use it whenever deciding how to publish a domain event, how to subscribe to one, or how to validate and handle failures in an event service.34Use it when determining whether the CulDeSac pattern is appropriate for a given service.3536## The CulDeSac pattern3738The CulDeSac pattern applies when a service needs to send a domain event outward without expecting a synchronous return from any subscriber.39It is a dead-end in the call graph: data flows in one direction, callers are not blocked waiting for a result, and subscribers are free to react independently.4041### Why use events42430. To decouple services across bounded contexts without creating direct service-to-service dependencies.441. To allow multiple subscribers to react to a single domain event independently.452. To avoid creating orchestration services that must know about every downstream consumer.463. To support fire-and-forget flows where the publisher does not need confirmation from subscribers.474. To enable downstream services to scale and evolve without affecting the publisher.485. To prevent any single orchestrator from accumulating too many responsibilities over time.4950## Event service doctrine51520. Event services are always foundation services.531. Event services sit at the boundary between the domain model and the event infrastructure.542. Event services must not depend on other services -- only on the event broker.553. Event services expose exactly two operations per entity: Publish[Entity]Async and SubscribeTo[Entity]Event.564. Event services do not inject a logging broker -- they have no logging dependency by design.575. Event services use two TryCatch delegates to isolate exception handling from business logic.586. Event services do NOT catch Dependency or CriticalDependency exceptions -- they do not call HTTP or storage APIs.5960## Broker implementation doctrine61620. The event broker is the only infrastructure dependency of an event service.631. The event broker wraps any chosen event infrastructure -- no specific event library is mandated.642. Each entity gets its own event client instance, typed to that entity, declared in the broker constructor.653. The broker is split into four files: base interface, entity interface partial, base implementation, entity implementation partial.664. Broker operations are thin pass-throughs to the underlying event infrastructure -- no logic lives in the broker.6768## Orchestration integration6970The event service is consumed exclusively by orchestration services.71Controllers, processing services, and other foundation services must never depend on an event service directly.7273### Publisher side7475An orchestration service that needs to raise a domain event calls `Publish[Entity]Async` on the event service.76The orchestration service owns the composition of the entity and delegates the publish call to the event service.77The publisher orchestration service knows nothing about which services will subscribe -- it only knows it must publish.7879### Subscriber side8081An orchestration service that needs to react to a domain event exposes a method named `SubscribeTo[Entity]Events` (plural).82That method calls `this.[entity]EventService.SubscribeTo[Entity]Event(handler)` where handler is a private async method.83The handler receives the entity and performs the orchestration reaction -- calling foundation services as needed.84Only the subscriber orchestration service knows what to do with the event; the publisher is unaware of subscribers.8586### Naming distinction: plural vs singular87880. The event service method is always singular: `SubscribeTo[Entity]Event`.891. The orchestration service wrapper method is always plural: `SubscribeTo[Entity]Events`.902. The plural wrapper exists because the orchestration service may route the event to multiple sub-operations.913. The plural wrapper is the method called at startup -- never the singular event service method directly.9293## Dependency injection and startup activation9495### Registration rules96970. All orchestration services that publish or subscribe must be registered in DI.981. The required DI lifetime for `IEventBroker` and `EventBroker` depends on the event infrastructure in use.992. For **in-memory event infrastructure** (such as LeVent): singleton is required for correctness -- client instances hold subscription handler registrations in memory; scoped or transient registrations produce fresh instances with no registered handlers, so events are never delivered.1003. For **external event infrastructure** (such as Azure Service Bus or EventHighway): follow the client library's own lifetime recommendations -- subscriptions live in the external service and survive independently of the client instance, but clients typically require singleton lifetime for connection reuse and to keep message processors alive.1014. `I[Entity]EventService` and `[Entity]EventService` must be registered with a lifetime that matches the broker they depend on -- never register the service with a longer lifetime than the broker.1025. When in doubt, prefer singleton -- it is safe for all known event infrastructure and avoids silent subscription failures.103104### Startup activation rules1051060. Event subscriptions are not self-activating -- they must be explicitly started at application startup.1071. After the DI container is built, every subscribing orchestration service must have its `SubscribeTo[Entity]Events()` method called.1082. The activation call must appear in `Configure()` (Startup.cs style) or its equivalent in a minimal API host setup.1093. In Startup.cs: use `app.ApplicationServices.GetService<I[Entity]OrchestrationService>().SubscribeTo[Entity]Events()`.1104. In minimal API (Program.cs): use `app.Services.GetService<I[Entity]OrchestrationService>().SubscribeTo[Entity]Events()` after `app = builder.Build()`.1115. If the startup activation call is missing, no subscriber will ever receive events -- the subscription is silently never registered.1126. Every subscribing orchestration service must have exactly one startup activation call per subscription.113114## Validation rules for event services115116### Publish validation1171180. Validate the entity is not null before publishing.1191. A null entity must throw NullEventException immediately (circuit-breaking).1202. Null entity validation must not collect further errors -- it breaks immediately.121122### Subscribe validation1231240. Validate the event handler delegate is not null before subscribing.1251. A null handler must throw NullEventHandlerException immediately (circuit-breaking).1262. Null handler validation must not collect further errors -- it breaks immediately.127128## Exception handling rules1291300. All validation failures must produce [Entity]EventValidationException wrapping the inner exception.1311. All unexpected exceptions must be wrapped in FailedEventServiceException, then in [Entity]EventServiceException.1322. FailedEventServiceException must carry the original exception as innerException and its Data collection.1333. Event services do not use DependencyValidationException or DependencyException categories.1344. TryCatch delegates must be used to separate exception handling from core logic.135136## Naming conventions1371380. Interface: I[Entity]EventService.1391. Implementation: [Entity]EventService (internal partial class).1402. Publish operation: Publish[Entity]Async (returns ValueTask, async).1413. Subscribe operation: SubscribeTo[Entity]Event (returns void, synchronous).1424. Subscribe must NEVER be named ListenTo[Entity]Event.1435. Broker interface: IEventBroker (shared), with entity-specific partials in IEventBroker_[Entity].cs.1446. Exception naming: Null[Entity]EventException, Null[Entity]EventHandlerException, Failed[Entity]EventServiceException, [Entity]EventValidationException, [Entity]EventServiceException.145146## Test rules for event services1471480. Test SubscribeTo[Entity]Events happy path first.1491. Test Publish[Entity]Async happy path second.1502. Test validation failures third (null handler, null entity).1513. Test service exceptions fourth (unexpected errors in both operations).1524. Event service tests must NOT declare a loggingBrokerMock -- there is no logging broker.1535. Subscribe handler mocks must use Mock<Func<[Entity], ValueTask>>.1546. Always end every test with eventBrokerMock.VerifyNoOtherCalls().1557. Use Times.Once for expected calls and Times.Never for calls skipped due to validation failures.