Process Flow of SetGroupCommand¶
Overview¶
This document describes the end-to-end flow for handling a SetGroupCommand against a sporting event aggregate, including validation, change detection, event persistence, and projection behavior.
Command parameter¶
SetGroupCommand (a DomainCommand<SportingEventId>) carries a single payload field:
- Group: required (
required string); the new group value to set.
The command is gated with [RequiresRoles(RoleDefinition.ContentManager)].
Aggregate receive and validation¶
SportingEventAggregate.Execute(SetGroupCommand) runs the following steps in order:
- Validation first:
Validate(SetGroupCommand)is invoked before any event construction. It throwsInvalidCommandExceptionwhen: - the event has not been created (
!isSportingEventCreated) -- "SportingEvent not created"; - the incoming
Groupis null, empty, or whitespace -- "SportingEvent Group is null, empty, or whitespace"; - the event is archived (
isArchived) -- "SportingEvent is archived". - Trim: the incoming
Groupis trimmed (command.Group.Trim()) before comparison and before it becomes the event payload. - Change detection: an event is raised only when
sportingEventDetailsis non-null and the trimmedGroupdiffers from the currentsportingEventDetails.Groupusing a case-insensitive comparison (StringComparison.OrdinalIgnoreCase). - State update:
Apply(GroupSetEvent)rebuildssportingEventDetailswith the newGroupand setsgroupManuallyOverridden = true, recording that the group was set explicitly rather than derived.
Change detection and event creation¶
- Compare the trimmed incoming Group against the existing
sportingEventDetails.GroupusingStringComparison.OrdinalIgnoreCase. - If
sportingEventDetailsis non-null and the values differ, construct and raise GroupSetEvent. The event (aDomainEvent<SportingEventId>) carries a single payload field: Group (the trimmed value). - If the values are identical (case-insensitive), or
sportingEventDetailsis null, no event is raised.
Note that the archive guard is enforced on the write side only: Validate rejects the command outright when isArchived is true, so an archived event never produces a GroupSetEvent.
Event persistence and publication¶
- A raised GroupSetEvent is appended to the event stream via the command executor/store.
- The event is then published to the event bus so projections and subscribers receive it.
Projection behavior and read model update¶
- SportingEventContractBuilder is a
SingleStreamProjection<SportingEventContract, Guid>that includesGroupSetEvent(alongside the create, update, start-time, archive, and restore events) and updates theSportingEventContractread model. - Upon receiving GroupSetEvent,
Apply(GroupSetEvent, SportingEventContract)produces an updated contract unconditionally: - sets
contract.GrouptodomainEvent.Group; - sets
contract.VersiontodomainEvent.Version. - There is no data-source gate on this apply: every
GroupSetEventupdates the read model's group.
Archive state on the read model is managed by separate events: SportingEventArchivedEvent sets IsArchived = true and SportingEventRestoredEvent sets IsArchived = false. The GroupSetEvent apply does not touch IsArchived; the archive check that prevents a group change on an archived event lives in the aggregate's Validate, upstream of the projection.
Read side visibility¶
- Consumers of
SportingEventContractobserve the updated Group for everyGroupSetEvent, regardless of originating data source. - Because archived events are rejected at command validation, a
GroupSetEventis only ever emitted (and therefore only ever applied to the read model) for non-archived events.