Home Posts Post Search Tag Search

Real World Event Sourcing - 05 - Building Event-Sourced Elixir Apps with Commanded
Published on: 2026-06-30 Tags: elixir, ETS, Systems, Game Logic, GameLoop, event-sourcing, cqrs, aggregates, software-architecture, Real World Event Sourcing, ECS, Commanded, EventHandler, ProcessManager

5. Building Event-Sourced Elixir Apps with Commanded

So far, you’ve played with the basic building blocks of event-sourced applications: commands, events, aggregates, projectors, and process managers. Now we can start to do more with Elixir Commanded Library.




Deciding Where to Start

You want to start small then build from there. I recommend taking small steps and thinking about what a user or the company will need.




Introducing Lunar Frontiers

We will build a space colony that is comprised of a text based game. Players will upload code that the robots will run, and as the designers we will have everything built with event sourced logging.


Embracing the Game Loop

We will have to use a game loop that will have injectors drop events into the “loop tick” and it will then propagate out from there.




Creating the First Flow

So the first thing that we want to build is a building, once the players robot places the building it will advance one tick per tick till the building is complete. Now although the end result will be a completed building and it will go through stages to get there it will be easier to “destroy” the building and then replace it with a completed building.


Now we can take the existing code that is within the given code and copy it to the project directory that we have it might look something like this. cp -R lunar_frontiers_0/ lunar_frontiers_1/

Creating the Construction Site Aggregate

We want to now create the Construction Site Aggregate, it should be able to receive the following commands:

• f(spawn_site) -> site_spawned—This creates a new instance of this aggregate.
• f(advance_construction) -> construction_progressed, construction_completed—This 
command is dispatched during a game loop tick advance to move construction
forward.

Now the construction site aggregate handles the following events:

• site_spawned—A new construction site has appeared.
• construction_progressed—The site has made progress in building toward its goal.
• construction_completed—The site is finished and it can set its internal state
to reflect this.

Create a new file in lib/lunar_frontiers/app/aggregates/construction_site.ex:

defmodule LunarFrontiers.App.Aggregates.ConstructionSite do
  alias LunarFrontiers.App.Events.{
    SiteSpawned,
    ConstructionProgressed,
    ConstructionCompleted
  }

  alias LunarFrontiers.App.Commands.{SpawnSite, AdvanceConstruction}
  alias __MODULE__
  alias Commanded.Aggregate.Multi

  defstruct [
    :site_id,
    :site_type,
    :location,
    :required_ticks,
    :completed_ticks,
    :created_tick,
    :player_id,
    :completed,
    :completed_tick
  ]

  # Command Handlers
  def execute(
        %ConstructionSite{} = _site,
        %SpawnSite{
          site_id: id,
          site_type: typ,
          completion_ticks: ticks,
          location: loc,
          tick: now_tick,
          player_id: player_id
        }
      ) do
    {:ok,
     %SiteSpawned{
       site_id: id,
       site_type: typ,
       location: loc,
       tick: now_tick,
       remaining_ticks: ticks,
       player_id: player_id
     }}
  end

  def execute(
        %ConstructionSite{} = site,
        %AdvanceConstruction{} = cmd
      ) do
    site
    |> Multi.new()
    |> Multi.execute(&progress_construction(&1, cmd.tick, cmd.advance_ticks))
    |> Multi.execute(&check_completed(&1, cmd.tick))
  end

  defp progress_construction(site, tick, ticks) do
    {:ok,
     %ConstructionProgressed{
       site_id: site.site_id,
       site_type: site.site_type,
       location: site.location,
       progressed_ticks: ticks,
       required_ticks: site.required_ticks,
       tick: tick
     }}
  end

  defp check_completed(
         %ConstructionSite{
           completed_ticks: c,
           required_ticks: r
         } = site,
         tick
       )
       when c >= r do
    %ConstructionCompleted{
      site_id: site.site_id,
      player_id: site.player_id,
      site_type: site.site_type,
      location: site.location,
      tick: tick
    }
  end

  defp check_completed(%ConstructionSite{}, _tick), do: []
  # State Mutators
  def apply(%ConstructionSite{} = _site, %SiteSpawned{
        site_id: id,
        site_type: typ,
        location: loc,
        tick: now_tick,
        remaining_ticks: ticks,
        player_id: player_id
      }) do
    %ConstructionSite{
      site_type: typ,
      site_id: id,
      player_id: player_id,
      location: loc,
      created_tick: now_tick,
      required_ticks: ticks,
      completed_ticks: 0,
      completed: false
    }
  end

  def apply(
        %ConstructionSite{} = site,
        %ConstructionProgressed{} = event
      ) do
    %ConstructionProgressed{progressed_ticks: progressed} = event

    %ConstructionSite{
      site
      | completed_ticks: site.completed_ticks + progressed
    }
  end

  def apply(
        %ConstructionSite{} = site,
        %ConstructionCompleted{} = event
      ) do
    %ConstructionSite{
      site
      | completed: true,
        completed_tick: event.tick
    }
  end
end

There is a lot of boiler plate here, but we can go over a bit of it now. Here are the 2 most important parts:

• execute—This processes a command and returns one or more events. In
the construction site code, you can see the use of Multi.new() to produce
multiple events, including an optional construction_completed event via an
elegant use of the pipeline operator.
• apply—This accepts an event and returns the appropriately modified state.

Creating the Building Aggregate

Create a new file in lib/lunar_frontiers/app/aggregates/building.ex:

defmodule LunarFrontiers.App.Aggregates.Building do
  alias LunarFrontiers.App.Events.BuildingSpawned
  alias LunarFrontiers.App.Commands.SpawnBuilding
  alias __MODULE__
  defstruct [:site_id, :site_type, :location, :player_id]

  def execute(%Building{} = _bldg, %SpawnBuilding{
        site_id: id,
        site_type: typ,
        location: loc,
        player_id: player_id
      }) do
    {:ok,
     %BuildingSpawned{
       site_id: id,
       site_type: typ,
       location: loc,
       player_id: player_id
     }}
  end

  def apply(%Building{} = _bldg, %BuildingSpawned{
        site_id: id,
        site_type: typ,
        location: loc,
        player_id: player_id
      }) do
    %Building{
      site_type: typ,
      site_id: id,
      player_id: player_id,
      location: loc
    }
  end
end

It will simply wait for the spawn command execute it and then update its state upon a new command.


Creating the Game Loop Aggregate

While you can make it so at any point an Injector can advance the game loop we want to make it so there is only source of truth for the game advance so we will build the gameloop.ex module. Create a new file.

defmodule LunarFrontiers.App.Aggregates.Gameloop do
  alias LunarFrontiers.App.Aggregates.Gameloop
  alias LunarFrontiers.App.Events.GameloopAdvanced
  alias LunarFrontiers.App.Commands.AdvanceGameloop
  alias __MODULE__
  defstruct [:game_id, :tick]

  def execute(
        %Gameloop{} = _loop,
        %AdvanceGameloop{tick: tick, game_id: id}
      ) do
    {:ok,
     %GameloopAdvanced{
       game_id: id,
       tick: tick
     }}
  end

  def apply(%Gameloop{} = _loop, %GameloopAdvanced{
        tick: tick,
        game_id: id
      }) do
    %Gameloop{
      game_id: id,
      tick: tick
    }
  end
end

Now we have a central way of dealing with an advancement of the gameloop.


Defining the Commands

There is code that is given with the book so we will just post what we want from the different files lib/lunar_frontiers/app/commands/.

• advance_construction—requests the advancement of a construction site. Contains
 the number of ticks to advance (allowing sped-up enhancement from
power-ups, and so on)
• advance_gameloop—requests the advancement of the game loop proper. The
corresponding event will trigger multiple component executions.
• spawn_building—requests the creation of a building
• spawn_site—requests the creation of a construction site

Defining the Events

• building_spawned—indicates that a building has appeared on the game board
(lunar surface)
• construction_completed—indicates a construction site has finished all necessary
work
• construction_progressed—indicates construction progress has moved forward
• gameloop_advanced—many, many other pieces of code hang on this event
like a finger pushing over a line of dominos
• site_spawned—indicates that a construction site has appeared

Managing the Construction Site

Okay this is something that we should definitely go over so create this file lib/lunar_frontiers/app/process_managers/construction.ex:

defmodule LunarFrontiers.App.ProcessManagers.Construction do
  alias LunarFrontiers.App.Events.{
    ConstructionCompleted,
    ConstructionProgressed,
    SiteSpawned,
    BuildingSpawned
  }

  alias LunarFrontiers.App.Commands.SpawnBuilding
  require Logger

  use Commanded.ProcessManagers.ProcessManager,
    application: LunarFrontiers.App.Application,
    name: __MODULE__

  @derive Jason.Encoder
  defstruct [:site_id, :tick_started, :ticks_completed, :ticks_required, :status]
  def interested?(%SiteSpawned{site_id: site_id}), do: {:start, site_id}

  def interested?(%ConstructionProgressed{site_id: site_id}),
    do: {:continue, site_id}

  def interested?(%ConstructionCompleted{site_id: site_id}),
    do: {:continue, site_id}

  def interested?(%BuildingSpawned{site_id: site_id}),
    do: {:stop, site_id}

  def interested?(_event), do: false
  # Command Dispatch
  def handle(
        %__MODULE__{},
        %ConstructionCompleted{
          site_type: site_type,
          site_id: site_id,
          location: location,
          player_id: player_id,
          tick: tick
        }
      ) do
    %SpawnBuilding{
      site_id: site_id,
      site_type: site_type,
      location: location,
      player_id: player_id,
      tick: tick
    }
  end

  # By default skip any problematic events
  def error(error, _command_or_event, _failure_context) do
    Logger.error(fn ->
      "#{__MODULE__} encountered an error: #{inspect(error)}"
    end)

    :skip
  end
end

This is the same as starting and stopping the PM for the Users. It will have 3 states at the end, :start: :continue, :stop.


Routing Commands and Events

The last thing is to have a set place for how and when to call which aggregates and injectors as well as the PM, this is handled by lib/lunar_frontiers/app/router.ex file:

#---
# Excerpted from "Real-World Event Sourcing",
# published by The Pragmatic Bookshelf.
# Copyrights apply to this code. It may not be used to create training material,
# courses, books, articles, and the like. Contact us if you are in doubt.
# We make no guarantees that this code is fit for any purpose.
# Visit https://pragprog.com/titles/khpes for more book information.
#---
defmodule LunarFrontiers.App.Router do
  alias LunarFrontiers.App.Commands.{
    AdvanceGameloop,
    AdvanceConstruction,
    SpawnSite,
    SpawnBuilding
  }

  alias LunarFrontiers.App.Aggregates.{
    Gameloop,
    ConstructionSite,
    Building
  }

  use Commanded.Commands.Router

  identify(Gameloop,
    by: :game_id,
    prefix: "game-"
  )

  identify(ConstructionSite,
    by: :site_id,
    prefix: "site-"
  )

  identify(Building,
    by: :site_id,
    prefix: "bldg-"
  )

  dispatch([AdvanceGameloop], to: Gameloop)
  dispatch([SpawnSite, AdvanceConstruction], to: ConstructionSite)
  dispatch([SpawnBuilding], to: Building)
end

Identify and dispatch macros are shortcuts this is originally described in systems_trigger.ex in the app/event_handlers folder.

defmodule LunarFrontiers.App.EventHandlers.SystemsTrigger do
  alias LunarFrontiers.App.Commands.AdvanceConstruction
  alias LunarFrontiers.App.Events.GameloopAdvanced
  alias LunarFrontiers.App.Application
  alias LunarFrontiers.App.Projectors.Building,
    as: BuildingProjector

  use Commanded.Event.Handler,
    application: Application,
    name: __MODULE__

  def handle(%GameloopAdvanced{tick: tick}, _metadata) do
    # Almost like an ECS, on each tick determine which systems need to be invoked/advanced
    # Some systems might not advance on each tick. For example, you could have combat advance
    # every other tick (tick mod 2), movement advance every tick, and resource generation every 3rd
    # tick (tick mod 3).

    advance_construction(tick)
    :ok
  end

  defp advance_construction(tick) do
    for site_id <- BuildingProjector.active_sites() do
      cmd = %AdvanceConstruction{
        site_id: site_id,
        tick: tick,
        advance_ticks: 1
      }

      Application.dispatch(cmd)
    end
  end
end

This is a multiplexer this will iterate over more than 1 pane/window/sessions. This is similar to the systems within the code for Hands on Rust. Where there is a single place that says how a game will advance for a give system.




Playing the Game

Okay so we have our unicycle (you don’t build a car right off the bat) let’s try and do the following:

• Advance game loop (tick 1)—This sets up the game and advances it to an
initial position.
• Spawn site (required ticks 2)—This sets up a construction site that will
take a total of 2 ticks to complete.
• Advance loop (tick 2)—Work progresses on the construction site.
• Advance loop (tick 3)—At this point, the construction site should magically
become a building. You’ll be able to confirm that in a number of ways.

iex -S mix

18:11:56.261 [debug] LunarFrontiers.App.Projectors.Building has successfully subscribed to event store

18:11:56.261 [debug] LunarFrontiers.App.ProcessManagers.Construction has successfully subscribed to event store

18:11:56.261 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger has successfully subscribed to event store
iex(1)> LunarFrontiers.App.Application.dispatch(
...(1)> %LunarFrontiers.App.Commands.AdvanceGameloop{game_id: 1, tick: 1})
## advanced the game loop 1 tick
18:12:20.255 [debug] Locating aggregate process for `LunarFrontiers.App.Aggregates.Gameloop` with UUID "game-1"

18:12:20.259 [debug] LunarFrontiers.App.Aggregates.Gameloop<game-1@0> executing command: %LunarFrontiers.App.Commands.AdvanceGameloop{game_id: 1, tick: 1}

18:12:20.270 [debug] LunarFrontiers.App.ProcessManagers.Construction received 1 event(s)

18:12:20.273 [debug] LunarFrontiers.App.ProcessManagers.Construction is not interested in event 1 ("game-1"@1)

18:12:20.273 [debug] LunarFrontiers.App.ProcessManagers.Construction confirming receipt of event: 1
:ok
## Now we are sending out all the receipts of the events 
18:12:20.275 [debug] LunarFrontiers.App.Aggregates.Gameloop<game-1@1> received events: [%Commanded.EventStore.RecordedEvent{event_id: "133abcce-3c2d-4545-ae01-aeae965dbe7d", event_number: 1, stream_id: "game-1", stream_version: 1, causation_id: "d9f334c9-b030-4513-bbfd-aafd498929f1", correlation_id: "95da65a4-7aaa-4b0c-824d-789fdd442f5c", event_type: "Elixir.LunarFrontiers.App.Events.GameloopAdvanced", data: %LunarFrontiers.App.Events.GameloopAdvanced{game_id: 1, tick: 1}, created_at: ~U[2026-06-30 01:12:20.262676Z], metadata: %{}}]

18:12:20.275 [debug] LunarFrontiers.App.Projectors.Building received events: [%Commanded.EventStore.RecordedEvent{event_id: "133abcce-3c2d-4545-ae01-aeae965dbe7d", event_number: 1, stream_id: "game-1", stream_version: 1, causation_id: "d9f334c9-b030-4513-bbfd-aafd498929f1", correlation_id: "95da65a4-7aaa-4b0c-824d-789fdd442f5c", event_type: "Elixir.LunarFrontiers.App.Events.GameloopAdvanced", data: %LunarFrontiers.App.Events.GameloopAdvanced{game_id: 1, tick: 1}, created_at: ~U[2026-06-30 01:12:20.262676Z], metadata: %{}}]

18:12:20.275 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger received events: [%Commanded.EventStore.RecordedEvent{event_id: "133abcce-3c2d-4545-ae01-aeae965dbe7d", event_number: 1, stream_id: "game-1", stream_version: 1, causation_id: "d9f334c9-b030-4513-bbfd-aafd498929f1", correlation_id: "95da65a4-7aaa-4b0c-824d-789fdd442f5c", event_type: "Elixir.LunarFrontiers.App.Events.GameloopAdvanced", data: %LunarFrontiers.App.Events.GameloopAdvanced{game_id: 1, tick: 1}, created_at: ~U[2026-06-30 01:12:20.262676Z], metadata: %{}}]

18:12:20.275 [debug] LunarFrontiers.App.Projectors.Building confirming receipt of event #1

18:12:20.275 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger confirming receipt of event #1
## Now we can spawn a site for the player with specific params
iex(2)> LunarFrontiers.App.Application.dispatch(
...(2)> %LunarFrontiers.App.Commands.SpawnSite{
...(2)> completion_ticks: 2, location: 1, player_id: 1,
...(2)> site_id: 1, site_type: 1, tick: 1
...(2)> })

18:12:31.162 [debug] Locating aggregate process for `LunarFrontiers.App.Aggregates.ConstructionSite` with UUID "site-1"

18:12:31.163 [debug] LunarFrontiers.App.Aggregates.ConstructionSite<site-1@0> executing command: %LunarFrontiers.App.Commands.SpawnSite{site_id: 1, player_id: 1, site_type: 1, completion_ticks: 2, location: 1, tick: 1}

18:12:31.163 [debug] LunarFrontiers.App.ProcessManagers.Construction received 1 event(s)
## PM has been created and has received its first event. Now it will create commands.
18:12:31.163 [debug] LunarFrontiers.App.Aggregates.ConstructionSite<site-1@1> received events: [%Commanded.EventStore.RecordedEvent{event_id: "785573fd-65f6-464d-8d13-2f34964afca6", event_number: 1, stream_id: "site-1", stream_version: 1, causation_id: "3771ba67-8085-41a5-b659-a4281bc1bb40", correlation_id: "7af108de-8959-406b-bb9a-3f5f3fdf941d", event_type: "Elixir.LunarFrontiers.App.Events.SiteSpawned", data: %LunarFrontiers.App.Events.SiteSpawned{site_id: 1, site_type: 1, location: 1, tick: 1, remaining_ticks: 2, player_id: 1}, created_at: ~U[2026-06-30 01:12:31.163245Z], metadata: %{}}]
:ok

18:12:31.163 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger received events: [%Commanded.EventStore.RecordedEvent{event_id: "785573fd-65f6-464d-8d13-2f34964afca6", event_number: 2, stream_id: "site-1", stream_version: 1, causation_id: "3771ba67-8085-41a5-b659-a4281bc1bb40", correlation_id: "7af108de-8959-406b-bb9a-3f5f3fdf941d", event_type: "Elixir.LunarFrontiers.App.Events.SiteSpawned", data: %LunarFrontiers.App.Events.SiteSpawned{site_id: 1, site_type: 1, location: 1, tick: 1, remaining_ticks: 2, player_id: 1}, created_at: ~U[2026-06-30 01:12:31.163245Z], metadata: %{}}]

18:12:31.163 [debug] LunarFrontiers.App.ProcessManagers.Construction is interested in event 2 ("site-1"@1)

18:12:31.163 [debug] LunarFrontiers.App.Projectors.Building received events: [%Commanded.EventStore.RecordedEvent{event_id: "785573fd-65f6-464d-8d13-2f34964afca6", event_number: 2, stream_id: "site-1", stream_version: 1, causation_id: "3771ba67-8085-41a5-b659-a4281bc1bb40", correlation_id: "7af108de-8959-406b-bb9a-3f5f3fdf941d", event_type: "Elixir.LunarFrontiers.App.Events.SiteSpawned", data: %LunarFrontiers.App.Events.SiteSpawned{site_id: 1, site_type: 1, location: 1, tick: 1, remaining_ticks: 2, player_id: 1}, created_at: ~U[2026-06-30 01:12:31.163245Z], metadata: %{}}]

18:12:31.163 [debug] LunarFrontiers.App.Projectors.Building confirming receipt of event #2

18:12:31.163 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger confirming receipt of event #2

18:12:31.163 [debug] LunarFrontiers.App.ProcessManagers.Construction confirming receipt of event: 2
## Now we advance the game 2 ticks there should be some buildings completed.
iex(3)> LunarFrontiers.App.Application.dispatch(
...(3)> %LunarFrontiers.App.Commands.AdvanceGameloop{
...(3)> game_id: 1, tick: 2})

18:12:44.338 [debug] Locating aggregate process for `LunarFrontiers.App.Aggregates.Gameloop` with UUID "game-1"

18:12:44.338 [debug] LunarFrontiers.App.Aggregates.Gameloop<game-1@1> executing command: %LunarFrontiers.App.Commands.AdvanceGameloop{game_id: 1, tick: 2}

18:12:44.338 [debug] LunarFrontiers.App.Aggregates.Gameloop<game-1@2> received events: [%Commanded.EventStore.RecordedEvent{event_id: "d80a8968-9f94-4d27-868b-76bf182c0699", event_number: 2, stream_id: "game-1", stream_version: 2, causation_id: "4ffbffa7-9fb2-49d6-8779-3ee319332f24", correlation_id: "d888ed72-f1db-4416-a25a-1eb76184ba70", event_type: "Elixir.LunarFrontiers.App.Events.GameloopAdvanced", data: %LunarFrontiers.App.Events.GameloopAdvanced{game_id: 1, tick: 2}, created_at: ~U[2026-06-30 01:12:44.338741Z], metadata: %{}}]
:ok

18:12:44.338 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger received events: [%Commanded.EventStore.RecordedEvent{event_id: "d80a8968-9f94-4d27-868b-76bf182c0699", event_number: 3, stream_id: "game-1", stream_version: 2, causation_id: "4ffbffa7-9fb2-49d6-8779-3ee319332f24", correlation_id: "d888ed72-f1db-4416-a25a-1eb76184ba70", event_type: "Elixir.LunarFrontiers.App.Events.GameloopAdvanced", data: %LunarFrontiers.App.Events.GameloopAdvanced{game_id: 1, tick: 2}, created_at: ~U[2026-06-30 01:12:44.338741Z], metadata: %{}}]
## Now the constructor will receive its events.
18:12:44.338 [debug] LunarFrontiers.App.ProcessManagers.Construction received 1 event(s)

18:12:44.338 [debug] LunarFrontiers.App.Projectors.Building received events: [%Commanded.EventStore.RecordedEvent{event_id: "d80a8968-9f94-4d27-868b-76bf182c0699", event_number: 3, stream_id: "game-1", stream_version: 2, causation_id: "4ffbffa7-9fb2-49d6-8779-3ee319332f24", correlation_id: "d888ed72-f1db-4416-a25a-1eb76184ba70", event_type: "Elixir.LunarFrontiers.App.Events.GameloopAdvanced", data: %LunarFrontiers.App.Events.GameloopAdvanced{game_id: 1, tick: 2}, created_at: ~U[2026-06-30 01:12:44.338741Z], metadata: %{}}]

18:12:44.338 [debug] LunarFrontiers.App.Projectors.Building confirming receipt of event #3

18:12:44.338 [debug] LunarFrontiers.App.ProcessManagers.Construction is not interested in event 3 ("game-1"@2)

18:12:44.338 [debug] Locating aggregate process for `LunarFrontiers.App.Aggregates.ConstructionSite` with UUID "site-1"

18:12:44.339 [debug] LunarFrontiers.App.ProcessManagers.Construction confirming receipt of event: 3
## Advancement of the ticks for site_id 1
18:12:44.339 [debug] LunarFrontiers.App.Aggregates.ConstructionSite<site-1@1> executing command: %LunarFrontiers.App.Commands.AdvanceConstruction{site_id: 1, tick: 2, advance_ticks: 1}
## Construction PM needs to know the updates.
18:12:44.339 [debug] LunarFrontiers.App.ProcessManagers.Construction received 1 event(s)

18:12:44.339 [debug] LunarFrontiers.App.ProcessManagers.Construction is interested in event 4 ("site-1"@2)

18:12:44.339 [debug] LunarFrontiers.App.Projectors.Building received events: [%Commanded.EventStore.RecordedEvent{event_id: "a6a42f8c-9340-46f5-af7c-45a32a1e277e", event_number: 4, stream_id: "site-1", stream_version: 2, causation_id: "99852a1d-c550-43e7-9533-938cef1bd230", correlation_id: "6597b204-3b45-4685-9538-258a8089605e", event_type: "Elixir.LunarFrontiers.App.Events.ConstructionProgressed", data: %LunarFrontiers.App.Events.ConstructionProgressed{site_id: 1, site_type: 1, location: 1, progressed_ticks: 1, required_ticks: 2, tick: 2}, created_at: ~U[2026-06-30 01:12:44.339085Z], metadata: %{}}]

18:12:44.339 [debug] LunarFrontiers.App.Aggregates.ConstructionSite<site-1@2> received events: [%Commanded.EventStore.RecordedEvent{event_id: "a6a42f8c-9340-46f5-af7c-45a32a1e277e", event_number: 2, stream_id: "site-1", stream_version: 2, causation_id: "99852a1d-c550-43e7-9533-938cef1bd230", correlation_id: "6597b204-3b45-4685-9538-258a8089605e", event_type: "Elixir.LunarFrontiers.App.Events.ConstructionProgressed", data: %LunarFrontiers.App.Events.ConstructionProgressed{site_id: 1, site_type: 1, location: 1, progressed_ticks: 1, required_ticks: 2, tick: 2}, created_at: ~U[2026-06-30 01:12:44.339085Z], metadata: %{}}]

18:12:44.339 [debug] LunarFrontiers.App.ProcessManagers.Construction confirming receipt of event: 4

18:12:44.339 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger confirming receipt of event #3

18:12:44.339 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger received events: [%Commanded.EventStore.RecordedEvent{event_id: "a6a42f8c-9340-46f5-af7c-45a32a1e277e", event_number: 4, stream_id: "site-1", stream_version: 2, causation_id: "99852a1d-c550-43e7-9533-938cef1bd230", correlation_id: "6597b204-3b45-4685-9538-258a8089605e", event_type: "Elixir.LunarFrontiers.App.Events.ConstructionProgressed", data: %LunarFrontiers.App.Events.ConstructionProgressed{site_id: 1, site_type: 1, location: 1, progressed_ticks: 1, required_ticks: 2, tick: 2}, created_at: ~U[2026-06-30 01:12:44.339085Z], metadata: %{}}]

18:12:44.339 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger confirming receipt of event #4
## Now we can advance even more. We will see some be interested and others not.
18:12:44.342 [debug] LunarFrontiers.App.Projectors.Building confirming receipt of event #4
iex(4)> LunarFrontiers.App.Application.dispatch(
...(4)> %LunarFrontiers.App.Commands.AdvanceGameloop{
...(4)> game_id: 1, tick: 3})

18:12:48.642 [debug] Locating aggregate process for `LunarFrontiers.App.Aggregates.Gameloop` with UUID "game-1"

18:12:48.643 [debug] LunarFrontiers.App.Aggregates.Gameloop<game-1@2> executing command: %LunarFrontiers.App.Commands.AdvanceGameloop{game_id: 1, tick: 3}

18:12:48.643 [debug] LunarFrontiers.App.Projectors.Building received events: [%Commanded.EventStore.RecordedEvent{event_id: "71b1dc8d-b719-4df6-9d6a-1d4e962409d1", event_number: 5, stream_id: "game-1", stream_version: 3, causation_id: "a6545c8c-b1ab-42a0-b6a0-863a9dcc2216", correlation_id: "88aada3c-2559-49f4-b44e-bd29492ad204", event_type: "Elixir.LunarFrontiers.App.Events.GameloopAdvanced", data: %LunarFrontiers.App.Events.GameloopAdvanced{game_id: 1, tick: 3}, created_at: ~U[2026-06-30 01:12:48.643133Z], metadata: %{}}]
:ok

18:12:48.643 [debug] LunarFrontiers.App.ProcessManagers.Construction received 1 event(s)

18:12:48.643 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger received events: [%Commanded.EventStore.RecordedEvent{event_id: "71b1dc8d-b719-4df6-9d6a-1d4e962409d1", event_number: 5, stream_id: "game-1", stream_version: 3, causation_id: "a6545c8c-b1ab-42a0-b6a0-863a9dcc2216", correlation_id: "88aada3c-2559-49f4-b44e-bd29492ad204", event_type: "Elixir.LunarFrontiers.App.Events.GameloopAdvanced", data: %LunarFrontiers.App.Events.GameloopAdvanced{game_id: 1, tick: 3}, created_at: ~U[2026-06-30 01:12:48.643133Z], metadata: %{}}]

18:12:48.643 [debug] LunarFrontiers.App.Projectors.Building confirming receipt of event #5

18:12:48.643 [debug] LunarFrontiers.App.Aggregates.Gameloop<game-1@3> received events: [%Commanded.EventStore.RecordedEvent{event_id: "71b1dc8d-b719-4df6-9d6a-1d4e962409d1", event_number: 3, stream_id: "game-1", stream_version: 3, causation_id: "a6545c8c-b1ab-42a0-b6a0-863a9dcc2216", correlation_id: "88aada3c-2559-49f4-b44e-bd29492ad204", event_type: "Elixir.LunarFrontiers.App.Events.GameloopAdvanced", data: %LunarFrontiers.App.Events.GameloopAdvanced{game_id: 1, tick: 3}, created_at: ~U[2026-06-30 01:12:48.643133Z], metadata: %{}}]
## More updates to the PM
18:12:48.643 [debug] LunarFrontiers.App.ProcessManagers.Construction is not interested in event 5 ("game-1"@3)

18:12:48.643 [debug] LunarFrontiers.App.ProcessManagers.Construction confirming receipt of event: 5

18:12:48.643 [debug] Locating aggregate process for `LunarFrontiers.App.Aggregates.ConstructionSite` with UUID "site-1"

18:12:48.643 [debug] LunarFrontiers.App.Aggregates.ConstructionSite<site-1@2> executing command: %LunarFrontiers.App.Commands.AdvanceConstruction{site_id: 1, tick: 3, advance_ticks: 1}

18:12:48.643 [debug] LunarFrontiers.App.ProcessManagers.Construction received 1 event(s)

18:12:48.643 [debug] LunarFrontiers.App.ProcessManagers.Construction is interested in event 6 ("site-1"@3)

18:12:48.643 [debug] LunarFrontiers.App.ProcessManagers.Construction confirming receipt of event: 6

18:12:48.643 [debug] LunarFrontiers.App.Projectors.Building received events: [%Commanded.EventStore.RecordedEvent{event_id: "4048ca62-8a90-426a-8fb7-8c30ce23e511", event_number: 6, stream_id: "site-1", stream_version: 3, causation_id: "d808f11b-2b87-46d0-85f8-6f242021a929", correlation_id: "fa87d035-b4ef-4134-89d0-ac11973fc6f6", event_type: "Elixir.LunarFrontiers.App.Events.ConstructionProgressed", data: %LunarFrontiers.App.Events.ConstructionProgressed{site_id: 1, site_type: 1, location: 1, progressed_ticks: 1, required_ticks: 2, tick: 3}, created_at: ~U[2026-06-30 01:12:48.643568Z], metadata: %{}}]

18:12:48.643 [debug] LunarFrontiers.App.Aggregates.ConstructionSite<site-1@4> received events: [%Commanded.EventStore.RecordedEvent{event_id: "4048ca62-8a90-426a-8fb7-8c30ce23e511", event_number: 3, stream_id: "site-1", stream_version: 3, causation_id: "d808f11b-2b87-46d0-85f8-6f242021a929", correlation_id: "fa87d035-b4ef-4134-89d0-ac11973fc6f6", event_type: "Elixir.LunarFrontiers.App.Events.ConstructionProgressed", data: %LunarFrontiers.App.Events.ConstructionProgressed{site_id: 1, site_type: 1, location: 1, progressed_ticks: 1, required_ticks: 2, tick: 3}, created_at: ~U[2026-06-30 01:12:48.643568Z], metadata: %{}}, %Commanded.EventStore.RecordedEvent{event_id: "978c4c4d-8f2c-4e0f-8b3e-d3da8eae5083", event_number: 4, stream_id: "site-1", stream_version: 4, causation_id: "d808f11b-2b87-46d0-85f8-6f242021a929", correlation_id: "fa87d035-b4ef-4134-89d0-ac11973fc6f6", event_type: "Elixir.LunarFrontiers.App.Events.ConstructionCompleted", data: %LunarFrontiers.App.Events.ConstructionCompleted{site_id: 1, site_type: 1, location: 1, tick: 3, player_id: 1}, created_at: ~U[2026-06-30 01:12:48.643568Z], metadata: %{}}]

18:12:48.643 [debug] LunarFrontiers.App.Projectors.Building confirming receipt of event #6

18:12:48.643 [debug] LunarFrontiers.App.ProcessManagers.Construction received 1 event(s)

18:12:48.643 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger confirming receipt of event #5

18:12:48.643 [debug] LunarFrontiers.App.ProcessManagers.Construction is interested in event 7 ("site-1"@4)

18:12:48.643 [debug] LunarFrontiers.App.Projectors.Building received events: [%Commanded.EventStore.RecordedEvent{event_id: "978c4c4d-8f2c-4e0f-8b3e-d3da8eae5083", event_number: 7, stream_id: "site-1", stream_version: 4, causation_id: "d808f11b-2b87-46d0-85f8-6f242021a929", correlation_id: "fa87d035-b4ef-4134-89d0-ac11973fc6f6", event_type: "Elixir.LunarFrontiers.App.Events.ConstructionCompleted", data: %LunarFrontiers.App.Events.ConstructionCompleted{site_id: 1, site_type: 1, location: 1, tick: 3, player_id: 1}, created_at: ~U[2026-06-30 01:12:48.643568Z], metadata: %{}}]

18:12:48.643 [debug] LunarFrontiers.App.Projectors.Building confirming receipt of event #7

18:12:48.644 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger received events: [%Commanded.EventStore.RecordedEvent{event_id: "4048ca62-8a90-426a-8fb7-8c30ce23e511", event_number: 6, stream_id: "site-1", stream_version: 3, causation_id: "d808f11b-2b87-46d0-85f8-6f242021a929", correlation_id: "fa87d035-b4ef-4134-89d0-ac11973fc6f6", event_type: "Elixir.LunarFrontiers.App.Events.ConstructionProgressed", data: %LunarFrontiers.App.Events.ConstructionProgressed{site_id: 1, site_type: 1, location: 1, progressed_ticks: 1, required_ticks: 2, tick: 3}, created_at: ~U[2026-06-30 01:12:48.643568Z], metadata: %{}}]

18:12:48.644 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger confirming receipt of event #6

18:12:48.644 [debug] LunarFrontiers.App.ProcessManagers.Construction attempting to dispatch command: %LunarFrontiers.App.Commands.SpawnBuilding{site_id: 1, player_id: 1, site_type: 1, location: 1, tick: 3}

18:12:48.644 [debug] Locating aggregate process for `LunarFrontiers.App.Aggregates.Building` with UUID "bldg-1"

18:12:48.644 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger received events: [%Commanded.EventStore.RecordedEvent{event_id: "978c4c4d-8f2c-4e0f-8b3e-d3da8eae5083", event_number: 7, stream_id: "site-1", stream_version: 4, causation_id: "d808f11b-2b87-46d0-85f8-6f242021a929", correlation_id: "fa87d035-b4ef-4134-89d0-ac11973fc6f6", event_type: "Elixir.LunarFrontiers.App.Events.ConstructionCompleted", data: %LunarFrontiers.App.Events.ConstructionCompleted{site_id: 1, site_type: 1, location: 1, tick: 3, player_id: 1}, created_at: ~U[2026-06-30 01:12:48.643568Z], metadata: %{}}]

18:12:48.644 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger confirming receipt of event #7

18:12:48.644 [debug] LunarFrontiers.App.Aggregates.Building<bldg-1@0> executing command: %LunarFrontiers.App.Commands.SpawnBuilding{site_id: 1, player_id: 1, site_type: 1, location: 1, tick: 3}

18:12:48.644 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger received events: [%Commanded.EventStore.RecordedEvent{event_id: "e7a9c1f7-80fe-4385-b932-d67c3abfd875", event_number: 8, stream_id: "bldg-1", stream_version: 1, causation_id: "2ffe10e8-991e-437b-9e71-1d60f6e5dbd1", correlation_id: "fa87d035-b4ef-4134-89d0-ac11973fc6f6", event_type: "Elixir.LunarFrontiers.App.Events.BuildingSpawned", data: %LunarFrontiers.App.Events.BuildingSpawned{site_id: 1, site_type: 1, location: 1, player_id: 1}, created_at: ~U[2026-06-30 01:12:48.644283Z], metadata: %{}}]

18:12:48.644 [debug] LunarFrontiers.App.Aggregates.Building<bldg-1@1> received events: [%Commanded.EventStore.RecordedEvent{event_id: "e7a9c1f7-80fe-4385-b932-d67c3abfd875", event_number: 1, stream_id: "bldg-1", stream_version: 1, causation_id: "2ffe10e8-991e-437b-9e71-1d60f6e5dbd1", correlation_id: "fa87d035-b4ef-4134-89d0-ac11973fc6f6", event_type: "Elixir.LunarFrontiers.App.Events.BuildingSpawned", data: %LunarFrontiers.App.Events.BuildingSpawned{site_id: 1, site_type: 1, location: 1, player_id: 1}, created_at: ~U[2026-06-30 01:12:48.644283Z], metadata: %{}}]

18:12:48.644 [debug] LunarFrontiers.App.EventHandlers.SystemsTrigger confirming receipt of event #8

18:12:48.644 [debug] LunarFrontiers.App.Projectors.Building received events: [%Commanded.EventStore.RecordedEvent{event_id: "e7a9c1f7-80fe-4385-b932-d67c3abfd875", event_number: 8, stream_id: "bldg-1", stream_version: 1, causation_id: "2ffe10e8-991e-437b-9e71-1d60f6e5dbd1", correlation_id: "fa87d035-b4ef-4134-89d0-ac11973fc6f6", event_type: "Elixir.LunarFrontiers.App.Events.BuildingSpawned", data: %LunarFrontiers.App.Events.BuildingSpawned{site_id: 1, site_type: 1, location: 1, player_id: 1}, created_at: ~U[2026-06-30 01:12:48.644283Z], metadata: %{}}]

18:12:48.644 [debug] LunarFrontiers.App.ProcessManagers.Construction confirming receipt of event: 7

18:12:48.644 [debug] LunarFrontiers.App.Projectors.Building confirming receipt of event #8

18:12:48.644 [debug] LunarFrontiers.App.ProcessManagers.Construction received 1 event(s)

18:12:48.644 [debug] LunarFrontiers.App.ProcessManagers.Construction has been stopped by event 8 ("bldg-1"@1)

18:12:48.644 [debug] LunarFrontiers.App.ProcessManagers.Construction confirming receipt of event: 8
## Now we can see the builds that are on the map.
iex(7)> :ets.tab2list(:buildings)
[
  {1,
   %{
     complete: 100.0,
     location: 1,
     site_id: 1,
     site_type: 1,
     player_id: 1,
     ready: true
   }}
]
## There are no sites under construction.
iex(8)> :ets.tab2list(:sites
...(8)> )
[]

So there is a lot going on here I tried to put as much notes before any real big block of code. Keep in mind that we are mostly just advancing the game tick and then the seeing all the needed messages being sent out.




Wrapping Up

This was a lot to go over in a short time but you should now be a bit more comfortable with how the messages are being sent and why.