This is an example project on how to build a CQRS/ES system using commanded. It is a simplified version of Event Sourcing as it does not include aggregates and commands.
It also demonstrates the difference between a CRUD system and an ES system when it comes to data modelling.
This project was subject to an Event Sourcing Talk held in January 2018 in Chiang Mai, Thailand.
The task is simple. Given a list of soccer matches, calculate the end season standings teable for every season and league. As an input, a sqlite database from kaggle is used (account needed): https://www.kaggle.com/mkhvalchik/soccer/data.
There are 3 possible solutions implemented in this project
Reads the matches and teams data model and calculates the standings in pure Elixir code
Using an elaborate SQL view to create the standings table from the data model
From a stream of events, build a projection
Download sqlite from kaggle (Ideally place the file in tmp/database.sqlite
)
Start Postgres database via Docker Compose:
docker-compose up
Install dependencies
mix deps.get
Create schema:
bin/setup.sh
Import CRUD data model:
mix seed_crud_from_sqlite ./tmp/database.sqlite
Import Event Source Stream
mix seed_eventstore_from_sqlite ./tmp/database.sqlite
Start server:
mix phx.server
Note: for event sourced view, wait until projection is updated
Go to localhost:4000
to select the season and leage
Play around with the different representations by changing the function all in the standings controller:
# CRUD application logic
render(
conn,
"show.html",
standings: StandingsView.crud_app(String.to_integer(season), String.to_integer(league_id))
)
# CRUD view logic
render(
conn,
"show.html",
standings: StandingsView.crud_view(String.to_integer(season), String.to_integer(league_id))
)
# Event Sourced
render(
conn,
"show.html",
standings: StandingsView.event_sourced(String.to_integer(season), String.to_integer(league_id))
)
If you're interested in the performance of the individual implementations, run the benchmark:
mix run lib/benchmark.exs