Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Event Sourcing - The Story Telling of Processes

Event Sourcing - The Story Telling of Processes

Almost every application has to deal with state. No matter if you like relational databases or prefer to go NoSQL the result is the same: your application only has one state.

Unfortunately, business requirements are not that static. They change constantly and so do the underlying processes. So how can you prepare your application to quickly adapt to changing processes without constantly migrating your database?

The key to event sourcing is recording what happened and build relevant data models of these events.

In this session, I will guide you through an example project that has been designed with event sourcing and how it compares to a classical CRUD application.

Leif Gensert

January 23, 2018
Tweet

More Decks by Leif Gensert

Other Decks in Programming

Transcript

  1. Event Sourcing
    The Story Telling
    of (Business) Processes

    View Slide

  2. View Slide

  3. Event Sourcing
    The Story Telling
    of (Business) Processes

    View Slide

  4. View Slide

  5. It’s a Tool

    View Slide

  6. Every single program can be reduced to
    Input
    Transformation
    Output
    * Former Boss

    View Slide

  7. Example Time

    View Slide

  8. View Slide

  9. Every team plays every other team twice
    306 Matches

    View Slide

  10. Home Team Away Team

    View Slide

  11. Home Team Away Team
    +3 +0

    View Slide

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

    View Slide

  13. View Slide

  14. View Slide

  15. Transformation
    Input
    Output

    View Slide

  16. Transformation
    Input
    Output

    View Slide

  17. Transformation
    Input
    Output

    View Slide

  18. Transformation
    Input
    Output
    ?

    View Slide

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

    View Slide

  20. Match
    Team1
    Team2
    League SoSo Soccer

    View Slide

  21. View Slide

  22. 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;

    View Slide

  23. 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;

    View Slide

  24. 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)

    View Slide

  25. Problem?

    View Slide

  26. Simplicity?

    View Slide

  27. Performance?

    View Slide

  28. Change!

    View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. View Slide

  33. View Slide

  34. View Slide

  35. View Slide

  36. T1
    T2
    T3

    View Slide

  37. T1
    T2
    T3

    View Slide

  38. There are only two
    hard things in
    Computer Science

    View Slide

  39. Rethink

    View Slide

  40. It’s a Tool

    View Slide

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

    View Slide

  42. View Slide

  43. TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded

    View Slide

  44. TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamQualified
    TeamQualified
    TeamQualified

    View Slide

  45. TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamQualified
    TeamQualified
    TeamQualified
    SeasonStarted

    View Slide

  46. MatchStarted
    MatchStarted
    MatchStarted
    TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamQualified
    TeamQualified
    TeamQualified
    SeasonStarted

    View Slide

  47. MatchStarted
    MatchStarted
    MatchStarted
    TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamQualified
    TeamQualified
    TeamQualified
    MachEnded
    MachEnded
    MachEnded
    SeasonStarted

    View Slide

  48. MatchStarted
    MatchStarted
    MatchStarted
    TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamFounded
    TeamQualified
    TeamQualified
    TeamQualified
    MachEnded
    MachEnded
    MachEnded
    SeasonEnded
    SeasonStarted

    View Slide

  49. View Slide

  50. 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

    View Slide

  51. View Slide

  52. (Re)Tell
    (Re)Tell
    (Re)Tell
    Capture

    View Slide

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

    View Slide

  54. … and tell a story
    from them

    View Slide

  55. Commands
    Aggregates
    Process
    Handlers

    View Slide

  56. Greg Young (yt/LDW0QWie21s)
    SoSo Soccer (gh/leifg/so_so_soccer)
    Commanded (gh/commanded)
    leif.io

    View Slide