Slide 1

Slide 1 text

Building Event Sourced Apps

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

My Example

Slide 5

Slide 5 text

Every team plays every other team twice 306 Matches

Slide 6

Slide 6 text

Home Team Away Team +3 +0 +1 +1

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Transformation Input Output ?

Slide 10

Slide 10 text

https://www.kaggle.com/mkhvalchik/soccer/data

Slide 11

Slide 11 text

Match Team1 Team2 League SoSo Soccer

Slide 12

Slide 12 text

sum(m.away_team_points) as points from (select *, case when away_team_goal > home_team_goal then 3 when home_team_goal = away_team_goal then 1 else 0 end as away_team_points, case when away_team_goal > home_team_goal then 1 else 0 end as away_team_wins, case when away_team_goal = home_team_goal then 1 else 0 end as away_team_draws, case when away_team_goal < home_team_goal then 1 else 0 end as away_team_losses from matches) m join leagues l on m.league_id = l.id group by season, away_team_api_id) t join leagues l on t.league_id = l.id join teams te on te.api_id = t.team_id group by (t.team_id, t.season) order by points desc, goal_difference desc, goals_for desc;

Slide 13

Slide 13 text

def standings(season, league_id) Repo.all(m in Match, where: m.season == ^season and m.league_id == ^league_id) |> Map.merge(%{row.home_team_api_id => home_team_values(row) }, &add_up/3) |> Map.merge(%{row.away_team_api_id => away_team_values(row) }, &add_up/3) |> Enum.sort_by(fn s -> {s.points, s.goal_difference, s.goals_for} end, &>=/2) end defp home_team_values(row) do %{ games: 1, wins: Rules.wins(row.home_team_goal, row.away_team_goal), draws: Rules.draws(row.home_team_goal, row.away_team_goal), losses: Rules.losses(row.home_team_goal, row.away_team_goal), goals_for: row.home_team_goal, goals_against: row.away_team_goal, points: Rules.points(row.home_team_goal, row.away_team_goal) } end defp add_up(_k, nil, v2), do: v2 defp add_up(_k, v1, v2) when is_integer(v1), do: v1 + v2 defp add_up(_k, v1, v2) when is_map(v1), do: Map.merge(v1, v2, &add_up/3)

Slide 14

Slide 14 text

Problem?

Slide 15

Slide 15 text

Simplicity?

Slide 16

Slide 16 text

Performance?

Slide 17

Slide 17 text

Change!

Slide 18

Slide 18 text

T1 T2 T3

Slide 19

Slide 19 text

There are only two hard things in Computer Science …

Slide 20

Slide 20 text

Rethink

Slide 21

Slide 21 text

It’s a Tool

Slide 22

Slide 22 text

It’s a Tool that helps you capture stuff that happened

Slide 23

Slide 23 text

MatchStarted MatchStarted MatchStarted TeamFounded TeamFounded TeamFounded TeamFounded TeamFounded TeamQualified TeamQualified TeamQualified MachEnded MachEnded MachEnded SeasonEnded SeasonStarted

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

project %TeamQualifiedForSeason{} = qualified do Ecto.Multi.insert(multi, :standings, %Standing{ season_id: qualified.season_id, team_api_id: qualified.team_api_id, team_long_name: qualified.team_long_name, league_id: qualified.league_id, sort_key: sort_key(0, 0, 0) }) end project %MatchEnded{} = ended do season_id = id_from_stream_id(ended.season_id) home_team = Standing.by_team_and_season(ended.home_team_api_id, season_id) away_team = Standing.by_team_and_season(ended.away_team_api_id, season_id) home_team_diff = team_changeset(home_team, ended.home_team_goal, ended.away_team_goal) away_team_diff = team_changeset(away_team, ended.away_team_goal, ended.home_team_goal) multi |> Ecto.Multi.update(:home_team, home_team_changeset) |> Ecto.Multi.update(:away_team, away_team_changeset) end

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

(Re)Tell (Re)Tell (Re)Tell Capture https://github.com/leifg/so_so_soccer

Slide 28

Slide 28 text

It’s a Tool that helps you capture events …

Slide 29

Slide 29 text

… and tell a story from them

Slide 30

Slide 30 text

“But I don’t like Football”

Slide 31

Slide 31 text

Let’s get some Calories

Slide 32

Slide 32 text

Demo Time

Slide 33

Slide 33 text

TrackMeal Meal MealTracked Command Aggregate Event ?

Slide 34

Slide 34 text

/api/do_stuff Command

Slide 35

Slide 35 text

/meals TrackMeal Frontend MealTracked Phx Channel

Slide 36

Slide 36 text

Hacker News Vanity

Slide 37

Slide 37 text

HnStory •StorySubmitted •StoryVotedOn •StoryHitFrontPage •StoryFrontPagePositionChanged

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

Offer Price: £1000 Buyer: E-Corp Status: “accepted” SELECT SUM(price) FROM offers WHERE status = ‘accepted’ GROUP BY buyer_id; Offer Price: £1000 Buyer: E-Corp Status: “paid” SELECT SUM(price) FROM offers WHERE status in (‘accepted’, ‘paid’) GROUP BY buyer_id;

Slide 42

Slide 42 text

Offer Price: £1000 Buyer: E-Corp Status: “accepted” SELECT SUM(price) FROM offers WHERE status = ‘accepted’ GROUP BY buyer_id; Offer Price: £1000 Buyer: E-Corp Status: “rejected”

Slide 43

Slide 43 text

Event Sourcing is Great for Side Projects It’s also Great for Real World Applications leif.io If you know the business bit.ly/buildingconduit