We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
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.