event sourcing - sdd conference › brands › sdd › library › event_sourcing.pdf · simple...

Post on 23-Jun-2020

6 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

EVENT SOURCINGMICHAEL NEWTON

@MAVNN LTD

MONOIDSErm... what?

SIMPLE RULESTake an operation (Op)Take a type ('T)Op : 'T -> 'T -> 'T (Closure)Op t1 (Op t2 t3) = Op (Op t1 t2) t3(Associativity)There is a t0 such that: Op t0 t1 = Op t1 t0 (Zero)

EXAMPLE 1 - INTEGER ADDITION1 + (2 + 3) = (1 + 2) + 3

1 + 0 = 0 + 1

EXAMPLE 2 - STRING CONCATINATION("Bob" + "Fred") + "Smith" = "Bob" + ("Fred" + "Smith")

"Bob" + "" = "" + "Bob"

POP QUIZ 1 - EMAIL SUCCESS COUNT1: 2: 3: 4: 5: 6: 7:

type EmailsSent = { SuccessCount : int FailCount : int }

let addEmailsSent es es' = { SuccessCount = es.SuccessCount + es'.SuccessCount FailCount = es.FailCount + es'.FailCount }

Monoid? Yes or no?

POP QUIZ 2 - MEANS1: 2: let mean (x : int) (y : int) = (float x + float y) / 2.0

Monoid? Yes or no?

FAIL!

MEANSNo closure: int -> int -> floatNot associative: mean 1 (mean 2 3) <> mean (mean 1 2) 3... even if you ignore the type error

BUT...1: 2: 3: 4: 5: 6: 7:

type MeanTracker = { Total : int Divisor : int }

let addMean mt mt' = { Total = mt.Total + mt'.Total Divisor = mt.Divisor + mt'.Divisor }

THAT'S GREAT......but why are we doing this?

BECAUSE LISTS.(or IEnumerable<'T>, if you're that way inclined)

REDUCEOF MAP/REDUCE FAME

1: 2: 3: 4: 5: 6: 7: 8: 9: 10:

let currentMean = [{ Total = 10; Divisor = 1 } { Total = 20; Divisor = 1 }] |> List.reduce addMean

// Some more data comes in... let newMean = [currentMean { Total = 15; Divisor = 1}] |> List.reduce addMean

Incremental and parallel processing are trivial

THIS COMPILES...1: 2: 3:

// No data yet... [] |> List.reduce addMean

PARTIAL FUNCTIONS ARE EVILA partial function is one that can't create a valid output forevery "valid" inputReduce is a partial function as it can't operate on emptylists

LET'S TURN IT UP TO ELEVEN

FOLD1: 2: 3:

// Still no data [] |> List.fold addMean { Total = 0; Divisor = 0 }

SUMMARY SO FARIf you have, or you can make a monoid:

You can always reduce lists down to a single summaryvalueYou can incrementally process the listFor (lots) more on monoids, read the series by ScottWlaschin

EVENT SOURCING?Nearly... but �rst!

GENERALISING FOLDaddMean had type: MeanTracker -> MeanTracker -> MeanTrackerBut the �rst parameter to fold has signature: 'State ->'T -> 'State

EXAMPLE "PROJECTION" FOLD1: 2: 3:

// Not a monoid operator - no closure let emailsSent total es = total + es.SuccessCount

INCREMENTAL SEND TOTALS! 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:

let currentOutput = [{ SuccessCount = 10; FailCount = 0 } { SuccessCount = 22; FailCount = 1 }] |> List.fold emailsSent 0

// More emails get sent let newCurrentOutput = [{ SuccessCount = 112; FailCount = 2 } { SuccessCount = 100; FailCount = 5 }] |> List.fold emailsSent currentOutput

And then...

...the customer tells you they want to know the percentagefailure rate of sends. Preferably with historic data. And, of

course, live updates.

NO PROBLEM!1: 2: 3: 4:

// Remember our MeanTracker object?let averageEmailSuccess mt es = { Total = mt.Total + es.SuccessCount Divisor = mt.Divisor + (es.SuccessCount + es.FailCount) }

INCREMENTAL AVERAGE SUCCESS RATES! 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

// We already have this data let currentRate = [{ SuccessCount = 10; FailCount = 0 } { SuccessCount = 22; FailCount = 1 } { SuccessCount = 112; FailCount = 2 } { SuccessCount = 100; FailCount = 5 }] |> List.fold averageEmailSuccess { SuccessCount = 0; FailCount = 0 }

// Now more starts coming in let newCurrentRate = [{ SuccessCount = 15; FailCount = 4 } { SuccessCount = 30; FailCount = 0 }] |> List.fold averageEmailSuccess currentRate

EVENT SOURCINGFinally...

EVENT SOURCING BASICSStore the domain events as "lists" (normally called streams)Build projections of them using folds

DOMAIN EVENTSA thing that has already happenedEmailSent; InvoicePaidDomain events can't fail - they've already happened!Compare with command: SendEmail; PayInvoice

SIMPLE INTERFACEno object relational impedance mismatch

SIMPLE INTERFACE1: 2: 3: 4: 5: 6: 7: 8: 9:

open System

type IRepository<'state, 'event> = abstract member LoadAggregate<'state> : Guid -> Async<'state * int> abstract member RefreshAggregate<'state> : Guid -> int -> 'state -> Async<'state * abstract member AppendEvents<'event> : Guid -> int -> 'event seq -> Async<int>

LET'S SEE AN EXAMPLE IN ACTION...

QUESTIONS?@mavnn ltd

blog

qvitoo

top related