Building Event Sourced Apps

Building Event Sourced Apps

Designing your software around a story!

What is Event Sourcing?

Instead of defining a global data model that fits all of your use cases think about the things your system does. Record the events that occur and then build multiple data models that fit your individual use cases.

Sounds very abstract? In this talk, Leif will elaborate on some side projects including use cases with the hope to encourage you to switch to more event-centric thinking.

84cd9fd20832381fc78084aac4cb4b6c?s=128

Leif Gensert

January 30, 2019
Tweet

Transcript

  1. Building Event Sourced Apps

  2. None
  3. None
  4. My Example

  5. Every team plays every other team twice 306 Matches

  6. Home Team Away Team +3 +0 +1 +1

  7. None
  8. None
  9. Transformation Input Output ?

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

  11. Match Team1 Team2 League SoSo Soccer

  12. 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;
  13. 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)
  14. Problem?

  15. Simplicity?

  16. Performance?

  17. Change!

  18. T1 T2 T3

  19. There are only two hard things in Computer Science …

  20. Rethink

  21. It’s a Tool

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

  23. MatchStarted MatchStarted MatchStarted TeamFounded TeamFounded TeamFounded TeamFounded TeamFounded TeamQualified TeamQualified

    TeamQualified MachEnded MachEnded MachEnded SeasonEnded SeasonStarted
  24. None
  25. 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
  26. None
  27. (Re)Tell (Re)Tell (Re)Tell Capture https://github.com/leifg/so_so_soccer

  28. It’s a Tool that helps you capture events …

  29. … and tell a story from them

  30. “But I don’t like Football”

  31. Let’s get some Calories

  32. Demo Time

  33. TrackMeal Meal MealTracked Command Aggregate Event ?

  34. /api/do_stuff Command

  35. /meals TrackMeal Frontend MealTracked Phx Channel

  36. Hacker News Vanity

  37. HnStory •StorySubmitted •StoryVotedOn •StoryHitFrontPage •StoryFrontPagePositionChanged

  38. None
  39. None
  40. None
  41. 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;
  42. 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”
  43. 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