Chronicle.Events.EventType behaviour (cratis_chronicle v3.16.1)

Copy Markdown View Source

Macro for defining Chronicle event types.

Use Chronicle.Events.EventType in an event struct module to annotate it with a stable event type identifier and generation number. Chronicle uses these to register the event schema and route events to the correct observers.

You can also declare model-bound constraints directly on the event type through attributes:

defmodule MyApp.Events.UserRegistered do
  use Chronicle.Events.EventType, id: "user-registered-v1"
  defstruct [:email]

  @unique :email
  unique_event_type()
end

defmodule MyApp.Events.UserDeleted do
  use Chronicle.Events.EventType, id: "user-deleted-v1"
  defstruct [:email]

  @remove_constraint "email"
end

Usage

defmodule MyApp.Events.AccountOpened do
  use Chronicle.Events.EventType, id: "account-opened-v1"
  defstruct account_id: "", owner_name: "", initial_balance: 0
end

With an explicit generation:

defmodule MyApp.Events.FundsDeposited do
  use Chronicle.Events.EventType, id: "funds-deposited", generation: 2
  defstruct account_id: "", amount: 0, currency: ""
end

Introspection

Modules that use Chronicle.Events.EventType expose metadata via __chronicle_event_type__/1:

MyApp.Events.AccountOpened.__chronicle_event_type__(:id)
#=> "account-opened-v1"

MyApp.Events.AccountOpened.__chronicle_event_type__(:generation)
#=> 1

Options

  • :id — the stable event type identifier (required, unless :generation_for is given)

  • :generation — the generation number (default 1; required when :generation_for is given)

  • :generation_for — marks this module as a previous generation of the event type module it names, instead of giving it its own :id. The event type id is resolved from the target module, so the two generations can never drift apart by a hand-typed, mismatched id string. Cannot be combined with :id; the target module must itself use Chronicle.Events.EventType.

    defmodule MyApp.Events.AccountOpenedV1 do
      use Chronicle.Events.EventType, generation_for: MyApp.Events.AccountOpened, generation: 1
      defstruct account_id: "", owner_name: ""
    end
  • :subject — the field holding the event's data subject, the person whose PII the event carries (subject: :customer_id). It is resolved on append unless an explicit :subject option is given, and decides whose erasure key protects the event's PII. Without it the kernel falls back to the event source id.

  • :tags — tags every append of this event type carries, merged with per-append :tags

  • :tombstone — marks the event type as a tombstone (the last event of an event source's life) (mirrors .NET's [Tombstone])

  • :public — makes the event type visible to other event stores, e.g. through event store subscriptions (mirrors [Public])

  • :compensation_for — the event type module this event compensates for (mirrors [CompensationFor<T>])

  • :types — type hints for nested fields; see Chronicle.Serialization

When you append an event, the client encodes its fields as camelCase JSON: owner_name is stored as ownerName. Give every field a typed default, such as initial_balance: 0; the JSON schema Chronicle validates appends against is generated from the defaults, and a nil default registers the field as a string.

Summary

Callbacks

Returns metadata for this event type module.

Functions

Declares that appending this event closes a stream scope (a closing event).

Resolves the data subject of an event from its declared :subject field, if it has one.

The tags every append of this event type carries (declared with tags:).

Returns whether module is a Chronicle event type (use Chronicle.Events.EventType).

Declares that appending this event removes a named constraint.

Declares that appending this event removes a named constraint, carrying the value to release.

Declares a unique constraint for one or more event fields.

Declares a unique constraint for the whole event type.

Callbacks

__chronicle_event_type__(key)

@callback __chronicle_event_type__(
  key ::
    :id
    | :generation
    | :generation_for
    | :constraints
    | :pii
    | :encrypted
    | :subject
    | :tags
    | :tombstone
    | :public
    | :compensation_for
) :: term()

Returns metadata for this event type module.

Accepts :id, :generation, or :constraints as the key.

Functions

closes_stream(opts \\ [])

(macro)

Declares that appending this event closes a stream scope (a closing event).

The kernel writes the event and the closure together; every later event inside the closed scope is refused with a stream-closed violation, while events elsewhere are unaffected. Use it when a domain fact ends something: a delivered report, closed books, a superseded version. Mirrors .NET's [ClosesStream]. Supported opts:

  • :dimensions - the participating dimensions, a list of :event_source_id, :event_source_type, :event_stream_type and :event_stream_id. Defaults to [:event_source_id, :event_stream_type, :event_stream_id].
  • :event_stream_id_from - the event field supplying the stream id instead of the append (implies the :event_stream_id dimension)
  • :reopened_by - event type modules that lift the closure of exactly this scope
  • :name - the owning constraint name (defaults to the event type id); declarations that share a name merge, and must use the same dimensions and stream id field
  • :event_sequences - restrict the declaration to specific event sequences (default: all)

Registering a declaration closes nothing in history; only later appends close scopes.

defmodule MyApp.Events.PeriodClosed do
  use Chronicle.Events.EventType, id: "period-closed"
  defstruct period: ""

  closes_stream event_stream_id_from: :period, reopened_by: [MyApp.Events.PeriodReopened]
end

declared_subject(event)

@spec declared_subject(struct()) :: String.t() | nil

Resolves the data subject of an event from its declared :subject field, if it has one.

An event type declares the field holding the subject (the person whose PII it carries) with use Chronicle.Events.EventType, id: "...", subject: :customer_id. A Chronicle.Concept value is unwrapped. Returns nil when no subject is declared or the field is empty.

declared_tags(arg1)

@spec declared_tags(struct()) :: [String.t()]

The tags every append of this event type carries (declared with tags:).

event_type?(module)

@spec event_type?(module()) :: boolean()

Returns whether module is a Chronicle event type (use Chronicle.Events.EventType).

Every append path checks this before building a request, so an event struct that never registered an event type fails with a structured {:error, {:unknown_event_type, module}} instead of raising deep inside request building.

remove_constraint(name)

(macro)

Declares that appending this event removes a named constraint.

This is equivalent to setting @remove_constraint.

remove_constraint(name, opts)

(macro)

Declares that appending this event removes a named constraint, carrying the value to release.

:properties names the constrained properties, in the same order as the unique/2 declaration, that this event carries the value of (empty or omitted releases every value for the event source, same as remove_constraint/1). Only meaningful for a :per_value unique constraint — a unique event type constraint has no constrained value to carry.

defmodule MyApp.Events.MappingVersionRemoved do
  use Chronicle.Events.EventType, id: "mapping-version-removed"
  defstruct [:version_id]

  remove_constraint("UniqueMappingVersionId", properties: [:version_id])
end

unique(fields, opts \\ [])

(macro)

Declares a unique constraint for one or more event fields.

This is equivalent to setting @unique. Supported opts:

  • :name — explicit constraint name (defaults to the first field name)
  • :mode — :per_event_source (default) keeps one value per event source and releases the previous value when it is replaced; :per_value keeps every value until a removal event (remove_constraint/1) releases it
  • :ignore_casing — compare values case-insensitively
  • :message — human-readable message to use when the constraint is violated
  • :scope — narrows uniqueness checking to values observed at append time along one or more dimensions, mirroring the C# client's IConstraintBuilder.PerEventSourceType()/PerEventStreamType()/ PerEventStreamId(). An atom (:per_event_source_type, :per_event_stream_type, or :per_event_stream_id) or a list of them. Defaults to unscoped (globally unique across the whole event store).
  • :event_sequences — restrict the declaration to specific event sequences (default: all)

:message also accepts a {Module, :function} callback invoked with the violation to compute one; see Chronicle.Events.ConstraintViolation.details/1.

An unknown option, an invalid :mode, or an invalid :scope value emits a compile warning (the declaration still compiles; an unknown :mode then raises ArgumentError when the constraint definition is built, as in earlier releases).

Scoping example

# Unique only within events for the same event source type — the same
# value may repeat across different event source types.
unique(:email, scope: :per_event_source_type)

unique_event_type(opts \\ [])

(macro)

Declares a unique constraint for the whole event type.

This is equivalent to setting @unique_event_type. Supported opts:

  • :name — explicit constraint name (defaults to the event type id)
  • :message — human-readable message to use when the constraint is violated, or a {Module, :function} callback; see unique/2
  • :scope — see unique/2
  • :event_sequences — see unique/2

An unknown option or an invalid :scope value emits a compile warning.