Skip to content

Closing stream constraint

Adorn the event type class with [ClosesStream] to close a stream scope whenever an event of that type is appended. The closure is written in the same step as the event, and later appends inside the scope are rejected with a StreamClosed violation. For the concept, the guarantees and when to prefer it over CompleteStream, see Closing events.

Chronicle discovers [ClosesStream]-adorned types automatically and registers the constraints with the Kernel when the client starts.

Choose the dimensions that make up the closed scope:

using Cratis.Chronicle.EventSequences;
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;
[EventType]
[ClosesStream(Dimensions = ClosedStreamDimensions.EventSourceId | ClosedStreamDimensions.EventStreamType)]
public record ConstraintsModelBoundClosingReportDelivered(string Recipient);

View C# snippet source on GitHub

Dimensions is a ClosedStreamDimensions flags value: EventSourceId, EventSourceType, EventStreamType and EventStreamId. Without it the scope is event source id, stream type and stream id of the append.

Set EventStreamIdFrom to the name of an event property to use its value as the stream id. This implies the EventStreamId dimension:

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;
[EventType]
[ClosesStream(EventStreamIdFrom = nameof(Period))]
public record ConstraintsModelBoundClosingBooksClosed(string Period, decimal ClosingBalance);

View C# snippet source on GitHub

The property must have a non-empty value on every append. The kernel refuses an append where it is missing, null or empty. Avoid a nullable property; the analyzer CHR0012 warns on nullable event properties.

Name sets the owner of the closure and defaults to the event type name. ReopenedBy lists the event types that remove this constraint’s closure for the exact scope they resolve to:

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;
[EventType]
[ClosesStream(Name = "close-month", EventStreamIdFrom = nameof(Month), ReopenedBy = [typeof(ConstraintsModelBoundClosingMonthReopened)])]
public record ConstraintsModelBoundClosingMonthClosed(string Month);
[EventType]
public record ConstraintsModelBoundClosingMonthReopened(string Month, string Reason);

View C# snippet source on GitHub

A reopening event type must carry the EventStreamIdFrom property too, or the client throws PropertyDoesNotExistOnEventType when it discovers the constraint.

PropertyMeaning
DimensionsDimensions of the closed scope. Defaults to event source id, stream type and stream id
EventStreamIdFromPayload property that provides the stream id
ReopenedByEvent types that reopen this constraint’s scope
NameOwner name. Defaults to the event type name. Cannot be empty
EventSequencesEvent sequences the declaration applies to. Empty means all

Declarations that share a Name merge into one constraint and must use the same dimensions and stream id property, otherwise the client throws ConflictingClosesStreamDeclarations.

Registering or changing the declaration does not close anything that is already in the log.