Skip to content

Unique event type constraint

Adorn the event type class with [Unique] to enforce that only one event of this type can be appended per event source. Use this when the event itself is a unique fact — for example, registering a user can only happen once.

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

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;
[EventType]
[Unique]
public record ConstraintsModelBoundUniqueEventTypeUserRegistered(string Email, string DisplayName);

An optional constraint name and violation message can be provided:

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;
[EventType]
[Unique(name: "UniqueUser", message: "A user with this identity has already been registered.")]
public record ConstraintsModelBoundUniqueEventTypeNamedUserRegistered(string Email, string DisplayName);

The message parameter is optional. Chronicle produces a default violation message when one is not supplied.

Apply [RemoveConstraint] to the event type that signals a domain object has been removed. When this event is appended, Chronicle releases the constraint, and the event source can accept a new event of this type:

using Cratis.Concepts;
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;
public record ConstraintsModelBoundUniqueEventTypeUserId(Guid Value) : ConceptAs<Guid>(Value);
[EventType]
[RemoveConstraint("UniqueUser")]
public record ConstraintsModelBoundUniqueEventTypeUserRemoved(ConstraintsModelBoundUniqueEventTypeUserId UserId);

The constraint name must exactly match the name provided to the [Unique] attribute. When no name was provided to [Unique], Chronicle uses the event type name as the default constraint name.

More than one event type can name the same constraint. Each of them releases it on its own, which is how a cycle that can end in more than one way is expressed with attributes.

The Chronicle Kernel reads the [Unique] and [RemoveConstraint] attributes when the client connects and creates the indexes it needs. Every subsequent append is checked against those indexes server-side, so no constraint logic runs in client code.