jonnifyelixir
F5 / F5.05 / events

F5.05 · part 2 of 3

Domain events

An event is a record that something happened, written in the past tense: %LearnerEnrolled{}, %LessonDelivered{}, %ProgressRecorded{}. It is a plain, immutable struct that carries the data of the change and the time it occurred — not a command to do something, but the fact that it was done. Where a command can fail, an event already happened and cannot be argued with. Naming changes this way gives the engine a vocabulary for its own history, and that history is exactly what the next dive folds back into state. Events carry their own branded id under an EVT namespace, so each is timestamped for free.

Three changes the Portal records. Select an event to see what happened and what it carries.

Facts, in the past tense

The tense is deliberate. A command is an imperative — enroll this learner — and might be refused. An event is a report — this learner was enrolled — and is settled. Because an event is immutable and self-describing, you can store it, replay it, or hand it to another part of the system without fear. Select an event.

Portal events · select one

LearnerEnrolled a learner joined a course LessonDelivered a lesson was served ProgressRecorded a learner advanced immutable · past tense · carries the data of the change

event: LearnerEnrolled

records: a learner joined a course

A command might not happen; an event already did. That difference is why events, not commands, are the thing worth keeping.

In code

An event is a struct like any domain type — enforced keys, a typespec — named in the past tense. The command builds one on success, stamping it with an EVT id whose snowflake gives the time for free.

defmodule Portal.Events.LearnerEnrolled do
  @enforce_keys [:id, :user_id, :course_id, :at]
  defstruct [:id, :user_id, :course_id, :at]

  @type t :: %__MODULE__{
          id: String.t(), user_id: String.t(),
          course_id: String.t(), at: DateTime.t()
        }
end

# enroll emits it on success — a past-tense fact, stamped with an EVT id and its time
defp enrolled_event(user_id, course_id) do
  id = Portal.ID.new("EVT")
  %LearnerEnrolled{id: id, user_id: user_id, course_id: course_id, at: Portal.ID.at(id)}
end

name the change

Past tense, immutable, self-describing — a fact you can store and replay.

stamped in time

Each event carries an EVT id whose snowflake decodes to at.

Next: the engine as a reducer — folding these events into the current state.

References

Primary sources for this lesson, and where it connects in the course.

Sources

Related in this course