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.
Defining a unique event type constraint
Section titled “Defining a unique event type constraint”using Cratis.Chronicle.Events;using Cratis.Chronicle.Events.Constraints;
[EventType][Unique]public record ConstraintsModelBoundUniqueEventTypeUserRegistered(string Email, string DisplayName);import io.cratis.chronicle.constraints.Uniqueimport io.cratis.chronicle.events.EventType
@EventType@Uniquedata class ConstraintsModelBoundUniqueEventTypeUserRegistered(val email: String, val displayName: String)import io.cratis.chronicle.constraints.Unique;import io.cratis.chronicle.events.EventType;
@EventType@Uniquerecord ConstraintsModelBoundUniqueEventTypeUserRegistered(String email, String displayName) {}defmodule MyApp.Events.ConstraintsModelBoundUniqueEventTypeUserRegistered do use Chronicle.Events.EventType, id: "constraints-model-bound-unique-event-type-user-registered"
defstruct [:email, :display_name]
unique_event_type()endTypeScript does not support this workflow yet.Constraint name and violation message
Section titled “Constraint name and violation message”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);import io.cratis.chronicle.constraints.Uniqueimport io.cratis.chronicle.events.EventType
@EventType@Unique(id = "UniqueUser", message = "A user with this identity has already been registered.")data class ConstraintsModelBoundUniqueEventTypeNamedUserRegistered(val email: String, val displayName: String)import io.cratis.chronicle.constraints.Unique;import io.cratis.chronicle.events.EventType;
@EventType@Unique(id = "UniqueUser", message = "A user with this identity has already been registered.")record ConstraintsModelBoundUniqueEventTypeNamedUserRegistered(String email, String displayName) {}defmodule MyApp.Events.ConstraintsModelBoundUniqueEventTypeNamedUserRegistered do use Chronicle.Events.EventType, id: "constraints-model-bound-unique-event-type-named-user-registered"
defstruct [:email, :display_name]
# Elixir supports a custom constraint name, but not a custom violation message. unique_event_type(name: "UniqueUser")endTypeScript does not support this workflow yet.The message parameter is optional. Chronicle produces a default violation message when one is not supplied.
Releasing a constraint
Section titled “Releasing a constraint”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);import io.cratis.chronicle.constraints.RemoveConstraintimport io.cratis.chronicle.events.EventType
@EventType@RemoveConstraint("UniqueUser")data class ConstraintsModelBoundUniqueEventTypeUserRemoved(val userId: String)import io.cratis.chronicle.constraints.RemoveConstraint;import io.cratis.chronicle.events.EventType;
@EventType@RemoveConstraint("UniqueUser")record ConstraintsModelBoundUniqueEventTypeUserRemoved(String userId) {}defmodule MyApp.Events.ConstraintsModelBoundUniqueEventTypeUserRemoved do use Chronicle.Events.EventType, id: "constraints-model-bound-unique-event-type-user-removed"
defstruct [:user_id]
remove_constraint("UniqueUser")endTypeScript does not support this workflow yet.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.
How constraints are enforced
Section titled “How constraints are enforced”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.