Closing stream constraint
Use ClosesStreamOn<TEvent> on the constraint builder 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 and the guarantees, see Closing events. For the attribute style, see the model-bound closing stream constraint.
Defining a closing constraint
Section titled “Defining a closing constraint”Choose the dimensions with the Per... methods on the configuration callback:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Events.Constraints;
[EventType]public record ConstraintsDeclarativeClosingReportDelivered(string Recipient);
public class ConstraintsDeclarativeClosingReportDelivery : IConstraint{ public void Define(IConstraintBuilder builder) => builder.ClosesStreamOn<ConstraintsDeclarativeClosingReportDelivered>( closes => closes.PerEventSourceId().PerEventStreamType());}| Method | Adds the dimension |
|---|---|
PerEventSourceId() | Event source id |
PerEventSourceType() | Event source type |
PerEventStreamType() | Event stream type |
PerEventStreamId() | Event stream id |
If you call none of them, the scope is event source id, stream type and stream id of the append.
Take the stream id from a property
Section titled “Take the stream id from a property”EventStreamIdFrom reads the stream id from an event property and implies the stream id dimension:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Events.Constraints;
[EventType]public record ConstraintsDeclarativeClosingBooksClosed(string Period, decimal ClosingBalance);
public class ConstraintsDeclarativeClosingBooks : IConstraint{ public void Define(IConstraintBuilder builder) => builder.ClosesStreamOn<ConstraintsDeclarativeClosingBooksClosed>( closes => closes.PerEventSourceId().EventStreamIdFrom(@event => @event.Period));}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.
Reopening events and the constraint name
Section titled “Reopening events and the constraint name”ReopenedBy<T>() names an event type that removes this constraint’s closure for the exact scope it resolves to. The optional name argument sets the owner of the closure and defaults to the event type name:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Events.Constraints;
[EventType]public record ConstraintsDeclarativeClosingMonthClosed(string Month);
[EventType]public record ConstraintsDeclarativeClosingMonthReopened(string Month, string Reason);
public class ConstraintsDeclarativeClosingMonth : IConstraint{ public void Define(IConstraintBuilder builder) => builder.ClosesStreamOn<ConstraintsDeclarativeClosingMonthClosed>( closes => closes .PerEventSourceId() .EventStreamIdFrom(@event => @event.Month) .ReopenedBy<ConstraintsDeclarativeClosingMonthReopened>(), name: "close-month");}The reopening event type must carry the same property as the closing event when EventStreamIdFrom is used.
Several closing events, one constraint
Section titled “Several closing events, one constraint”Declarations that share a name merge into one constraint, so a scope can be closed by more than one event type. They must use the same dimensions and stream id property, otherwise the client throws ConflictingClosesStreamDeclarations:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Events.Constraints;
[EventType]public record ConstraintsDeclarativeClosingReportDeliveredToClient(string Version);
[EventType]public record ConstraintsDeclarativeClosingReportSuperseded(string Version);
public class ConstraintsDeclarativeClosingVersions : IConstraint{ public void Define(IConstraintBuilder builder) { // Two declarations that share a name merge into one constraint. They must use the same dimensions. builder.ClosesStreamOn<ConstraintsDeclarativeClosingReportDeliveredToClient>( closes => closes.PerEventSourceId().EventStreamIdFrom(@event => @event.Version), name: "close-report-version"); builder.ClosesStreamOn<ConstraintsDeclarativeClosingReportSuperseded>( closes => closes.PerEventSourceId().EventStreamIdFrom(@event => @event.Version), name: "close-report-version"); }}Registering or changing the declaration does not close anything that is already in the log.