Session: Event Sourcing Applicative Profunctors - Jeremy Chassaing
Event Sourcing in function programming
decide
pure function, immutable objects
evolve takes 1 event applies it to the state
Link it together
-–
initial state
-–
now we have Event Sourcing
command: only what is needed to ‘decide’ event: only what is needed to ’evolve'
cf Aggregate
Take any aggregate, and it has enough information to run, without needing external data -we can rewrite any aggregate to this -without needing external data
-– Decider
decide: command -> event[] -> event[]
Decider composed of 6 things
-Command -Event -State (list of events, or denormalized) -decide : Cmd -> State -> Event[] -evolve: State -> Event -> State -initialState: State
-isTerminal: State -> bool # sometimes you want to end (suggested) eg “close the books, and start a new fiscal year”
-– Easy way
load state evolve state save state log / persist events -not used for continual state -used when big state changes (maintenance)
performance: only load&save every 10 events
-–
“progressive way of event sourcing”
no assumptions about - what command is - what event is - what decide is - what evolve is
-– Communicate outside aggregate boundary
example domain: todo-list
Bad pattern: state is ’todo list'
“I can’t use this, I need to save events I get from the outside”
Eventstore: events in exact order CosmoDB: partitions are not ordered -> need to save the order in which you noticed the events
External events Ingest InternalEvents evolve state
if external source is ordered, no need to save ‘internal events’ if not ; you need to save internal events
React state + drift : Event
if empty drift (drift is optional) returns all commands that need to happen (to recover from crash?)
initial state + isTerminal (to check if state needs to be cleaned up)
Process
-–
Actor –> Process
-–
Aggregate: inside needs to be consistent smaller boundary = better!
outside does not have to be consistent, but can be
we can split it whenever we want but we can combine them for now (as long as we don’t make connections between them that make it impossible to split them later)
-–
Example: Game Uno Game Hand 1 Hand 2 Hand 3 Stockpile
V1, everything async would be overkill
-–
Functor you can have a select or map operation so that if you have a function A:string -> B:int list of strings -> list of int
-–
Co-variant functor function A -> B F -> F
select / map : (f : a -> b) (List) = List
c -> (state -> eventlist) is incompatible with c-> d
decide f
compose a function that takes d
Game (GameCommand) StockPile (stockpileCommand) Hands ()
-> some ?? -> GameDecider -> some ?? -> StockpileDecider
or if none -> empty list
-–
GameEvent StockpileEvent …
-> some -> evolve Event + State -> None
GameDecicer -> GameEvent -> GameEvent StockpileDecicer -> StockpileEvent -> StockpileEvent
-–
Commandx, Eventx, ?x, dx, ec, initialStatex, isTerminalx
Make a pair of both states
Cx -> DecideX Cx StateX |> map Eventx Cy -> Decide of Cy StateY |> map EventY
evolve: E S -> match E with | Event for x -> evolve EventForX StateX EventForX |> {s with x = evolve EventForX StateX }
-–
> > at this point I dropped off, next session was starting >