The Standard Events
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
IEventBrokerandEventBrokerdepends 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]EventServiceand[Entity]EventServicemust 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()afterapp = 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.