ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

Erlang Event Sourcing Aggregate State Reconstruction

Erlang

Goal -- WPM

Ready
Exercise Algorithm Area
1-module(cart_aggregate).
2
3-export([apply_event/2, reconstruct/1]).
4
5%% Event types (atoms)
6-define(event_cart_created, cart_created).
7-define(event_item_added, item_added).
8-define(event_item_removed, item_removed).
9-define(event_cart_checked_out, cart_checked_out).
10
11%% Aggregate State Structure
12%% State = #{ items => #{ ItemId => Quantity }, total_price => Price, status => Status }
13%% Status = created | checked_out
14
15%% Apply an event to the current state to get the new state.
16apply_event(State, Event = {?event_cart_created, CartId}) ->
17#{ items => #{}, total_price => 0, status => created, cart_id => CartId }.
18
19apply_event(State = #{ status := created }, Event = {?event_item_added, ItemId, Quantity, PricePerItem}) ->
20CurrentItems = maps:get(items, State, #{}),
21UpdatedQuantity = maps:get(ItemId, CurrentItems, 0) + Quantity,
22NewItems = maps_put(ItemId, UpdatedQuantity, CurrentItems),
23CurrentTotalPrice = maps:get(total_price, State, 0),
24ItemTotalPrice = Quantity * PricePerItem,
25NewTotalPrice = CurrentTotalPrice + ItemTotalPrice,
26State#{ items => NewItems, total_price => NewTotalPrice }.
27
28apply_event(State = #{ status := created }, Event = {?event_item_removed, ItemId, Quantity}) ->
29CurrentItems = maps:get(items, State, #{}),
30CurrentQuantity = maps:get(ItemId, CurrentItems, 0),
31if CurrentQuantity == undefined orelse CurrentQuantity < Quantity ->
32%% Cannot remove more items than available, or item not present
33State;
34else ->
35UpdatedQuantity = CurrentQuantity - Quantity,
36NewItems = if UpdatedQuantity == 0 -> maps_delete(ItemId, CurrentItems);
37else -> maps_put(ItemId, UpdatedQuantity, CurrentItems)
38end,
39%% Note: Price calculation for removal is omitted for simplicity,
40%% assuming price is fixed or handled by a separate event.
41State#{ items => NewItems }
42end.
43
44apply_event(State = #{ status := created }, Event = ?event_cart_checked_out) ->
45State#{ status => checked_out }.
46
47%% Handle unknown events or events applied to a non-applicable state
48apply_event(State, _UnknownEvent) ->
49%% In a real system, this might log an error or return an error tuple.
50State.
51
52%% Reconstruct the aggregate state from a list of events.
53reconstruct(Events) ->
54%% Start with an empty initial state (or a known initial state if applicable).
55%% For cart_created, the first event defines the initial state.
56case Events of
57[] -> #{};
58[FirstEvent | RestEvents] ->
59InitialState = apply_event(#{}, FirstEvent), % Apply the first event to an empty map
60lists:foldl(fun(Event, AccState) -> apply_event(AccState, Event) end, InitialState, RestEvents)
61end.
Algorithm description viewbox

Erlang Event Sourcing Aggregate State Reconstruction

Algorithm description:

This Erlang code implements the core logic for an event sourcing aggregate, specifically a shopping cart. The `apply_event/2` function takes the current state of the aggregate and an event, returning the new state after applying the event's effects. The `reconstruct/1` function takes a list of historical events and replays them sequentially using `apply_event/2` to rebuild the aggregate's current state. This pattern is fundamental to event sourcing, allowing the system's state to be derived solely from a sequence of immutable events.

Algorithm explanation:

The `cart_aggregate` module defines functions for managing the state of a shopping cart using event sourcing principles. The `apply_event/2` function is a pure function that takes the current aggregate state (represented as a map) and an event (also a map or tuple) and returns the new state. It uses pattern matching to handle different event types (`cart_created`, `item_added`, `item_removed`, `cart_checked_out`) and updates the state accordingly, ensuring that events are only applied to valid states (e.g., items can only be added/removed if the cart is `created`). The `reconstruct/1` function takes a list of events and builds the current state by applying the first event to an initial empty state, then folding over the remaining events, applying each one to the accumulating state. This ensures that the state is always derived from its history. Time complexity for `apply_event/2` is O(1) for simple events and O(N) for `item_added`/`item_removed` if map operations are considered O(log N) or O(N) in worst case for list operations. `reconstruct/1` has a time complexity of O(E * A), where E is the number of events and A is the average complexity of `apply_event/2`. Space complexity is O(S) where S is the size of the aggregate state.

Pseudocode:

Aggregate State:
  Map: {items => Map of ItemId -> Quantity, total_price => Number, status => String}.

apply_event(State, Event):
  If Event is 'cart_created':
    Initialize State with empty items, 0 price, status 'created', and CartId.
  If Event is 'item_added' and State.status is 'created':
    Update item quantity in State.items.
    Update State.total_price.
  If Event is 'item_removed' and State.status is 'created':
    If item exists and quantity is valid:
      Update item quantity in State.items.
      (Optionally update total_price if applicable).
  If Event is 'cart_checked_out' and State.status is 'created':
    Set State.status to 'checked_out'.
  For any other event or invalid state:
    Return current State (or error).

reconstruct(Events):
  If Events is empty, return empty State.
  Apply the first Event to an initial empty State to get InitialState.
  For each subsequent Event in Events:
    Apply Event to the current State to get the NewState.
    Update current State to NewState.
  Return the final State.