SlideShare a Scribd company logo
1 of 132
Download to read offline
Itamar Ravid - @itrvd 1
About me
โ€ข Freelance software developer
โ€ข Doing functional programming, microservices, DevOps
โ€ข Co-organize the Underscore Scala meetup
โ€ข Hit me up if you want to work together!
Itamar Ravid - @itrvd 2
Agenda
โ€ข Why Event Sourcing and CQRS are worth your time
โ€ข How we can use functions to implement them
โ€ข How abstractions from FP help us
โ€ข (shortly) Hooking this up to a stream
Itamar Ravid - @itrvd 3
Event Sourcing
CQRS
Itamar Ravid - @itrvd 4
Itamar Ravid - @itrvd 5
Itamar Ravid - @itrvd 6
Itamar Ravid - @itrvd 7
Itamar Ravid - @itrvd 8
Itamar Ravid - @itrvd 9
Itamar Ravid - @itrvd 10
Itamar Ravid - @itrvd 11
Itamar Ravid - @itrvd 12
Itamar Ravid - @itrvd 13
That's not the only
downside!
โ€ข No determinism
โ€ข Lowly available
โ€ข High coupling, barely autonomous
Itamar Ravid - @itrvd 14
What can we do to ๏ฌx this?
Itamar Ravid - @itrvd 15
Itamar Ravid - @itrvd 16
Itamar Ravid - @itrvd 17
Itamar Ravid - @itrvd 18
Itamar Ravid - @itrvd 19
Itamar Ravid - @itrvd 20
Focus on behaviors.
The objects will follow.
Itamar Ravid - @itrvd 21
Itamar Ravid - @itrvd 22
Itamar Ravid - @itrvd 23
Itamar Ravid - @itrvd 24
Responding to queries
Itamar Ravid - @itrvd 25
Responding to queries
Itamar Ravid - @itrvd 26
Materialized views
Itamar Ravid - @itrvd 27
CQRS: Command/Query Responsibility
Separation
We store the events in an append only stream.
We handle reads using a specialized store, derived from the
events.
Now you know CQRS!
Itamar Ravid - @itrvd 28
What do we gain?
โ€ข Faster response time
โ€ข Availability
โ€ข Determinism, take your debugging home
โ€ข
!
Event-sourced services are like pure functions!
Itamar Ravid - @itrvd 29
And speaking of pure functions
Let's see some code!
The examples ahead assume:
import cats._, cats.data._, cats.implicits._
Itamar Ravid - @itrvd 30
Zoom in
We'll now focus on the reservation service.
Let's event-storm this!
What commands/events are we going to use?
Itamar Ravid - @itrvd 31
Commands
We might be asked to:
โ€ข Create a reservation
โ€ข Add a person to a reservation
โ€ข Cancel a reservation
Itamar Ravid - @itrvd 32
Events
And these will cause the:
โ€ข Reservation to be created
โ€ข Reservation to be updated
โ€ข Reservation to be canceled
Itamar Ravid - @itrvd 33
Command De๏ฌnitions
I promised some code, right?
sealed trait ReservationCommand
case class Create(roomId: String, guests: Int)
extends ReservationCommand
case class ModifyGuests(reservationId: String, amount: Int)
extends ReservationCommand
case class Cancel(reservationId: String)
extends ReservationCommand
Itamar Ravid - @itrvd 34
Event De๏ฌnitions
And we have the corresponding events:
sealed trait ReservationEvent
case class Created(id: String, roomId: String, guests: Int)
extends ReservationEvent
case class Updated(id: String, roomId: String, guests: Int)
extends ReservationEvent
case class Canceled(id: String, roomId: String, guests: Int)
extends ReservationEvent
Itamar Ravid - @itrvd 35
Itamar Ravid - @itrvd 36
Itamar Ravid - @itrvd 37
Itamar Ravid - @itrvd 38
Itamar Ravid - @itrvd 39
Itamar Ravid - @itrvd 40
Itamar Ravid - @itrvd 41
Breakdown
So technically, we need:
1. Validation of commands
2. State representation, state mutation
3. Event generation
4. Persistence
Itamar Ravid - @itrvd 42
Itamar Ravid - @itrvd 43
Itamar Ravid - @itrvd 44
Validation
So we want a function, right?
def processCommand(command: ReservationCommand): ???
Itamar Ravid - @itrvd 45
Validation
So we want a function, right?
def processCommand(command: ReservationCommand): Either[Error, Unit]
Itamar Ravid - @itrvd 46
Validation
So we want a function, right?
type Error = String
type Result[A] = Either[Error, A]
def processCommand(command: ReservationCommand): Result[Unit]
Itamar Ravid - @itrvd 47
Validation
Let's start with simple validation: the number of guests should be
positive.
def validateGuests(command: Create): Result[Unit] =
if (command.guests <= 0) Left("Non-positive guests")
else Right(())
Itamar Ravid - @itrvd 48
Stateful validation
Next, we want a stateful validation - no duplicate reservations.
Itamar Ravid - @itrvd 49
Stateful validation
Next, we want a stateful validation - no duplicate reservations.
To do that, we need some sort of state:
case class Reservation(id: String)
trait Reservations {
def byRoomId(id: String): Option[Reservation]
}
Itamar Ravid - @itrvd 50
Validation
And here's our validation:
def validateDup(reservations: Reservations,
command: Create): Result[Unit] =
reservations.byRoomId(command.roomId) match {
case Some(_) => Left("Duplicate reservation")
case None => Right(())
}
Itamar Ravid - @itrvd 51
Validation - continued
Our command processing now looks like this:
def processCommand(reservations: Reservations,
command: ReservationCommand): Result[Unit] =
command match {
case create: Create =>
(validateGuests(create), validateDup(create, reservations))
.tupled.void
// case modifyGuests, case cancelReservation, etc.
}
Itamar Ravid - @itrvd 52
Validation - continued
What's that (a, b).tupled function?
This will combine the result of both validations into a single
value:
val guestValidation: Result[Unit] = validateGuests(create)
val dupValidation: Result[Unit] = validateDup(create, reservations)
val tupled: Result[(Unit, Unit)] = (guestValidation, dupValidation).tupled
Itamar Ravid - @itrvd 53
Validation - continued
What's that (a, b).tupled function?
This will combine the result of both validations into a single
value:
val guestValidation: Result[Unit] = validateGuests(create)
val dupValidation: Result[Unit] = validateDup(create, reservations)
val tupled: Result[(Unit, Unit)] = (guestValidation, dupValidation).tupled
If one of them fails - both fails too.
Itamar Ravid - @itrvd 54
Validation - continued
What else can we say about Result?
Itamar Ravid - @itrvd 55
Validation - continued
What else can we say about Result?
It's an Applicative - so we can use tupled.
Itamar Ravid - @itrvd 56
Validation - continued
What else can we say about Result?
It's also a Monad, so we can use for comprehensions:
for {
a <- validate1(cmd)
b <- validate2(cmd, a)
} yield f(a, b)
Itamar Ravid - @itrvd 57
Validation - continued
A few side notes:
โ€ข Our validation unfortunately short circuits. We don't see all
errors.
Check out cats.data.Validated for a solution.
โ€ข Applicative s are essential for joining independent
computations. De๏ฌnitely read up on them!
Check out the mapN, *>, <* combinators.
Itamar Ravid - @itrvd 58
Validation - summary
This is all I'm going to say about validation!
โ€ข Write an Either returning function
โ€ข Give it what it needs to compute
โ€ข Combine with the Applicative combinators
โ€ข Pro๏ฌt Validated!
Itamar Ravid - @itrvd 59
Itamar Ravid - @itrvd 60
Generating events
The path of least resistance is another function for generating:
def events(reservations: Reservations,
command: ReservationCommand): List[ReservationEvent]
Itamar Ravid - @itrvd 61
Generating events
We return the events inside Result:
def processCommand(reservations: Reservations,
command: ReservationCommand)
: Result[List[ReservationEvent]] =
command match {
case create: Create =>
(validateGuests(create), validateDup(create, reservations))
.tupled
.map(_ => events(reservations, commands))
}
Since we're using List, we can output 0, 1 or many events.
Itamar Ravid - @itrvd 62
Generating events
This is great if we can separate validation and event generation.
Itamar Ravid - @itrvd 63
Generating events
This is great if we can separate validation and event generation.
This isn't always possible!
Itamar Ravid - @itrvd 64
Generating events
This is great if we can separate validation and event generation.
This isn't always possible!
We'll upgrade the two functions with event generation.
Itamar Ravid - @itrvd 65
Generating events
Itamar Ravid - @itrvd 66
Generating events
There's a cool data type called WriterT we can use here:
case class WriterT[F[_], Log, Value](run: F[(Log, Value)])
Itamar Ravid - @itrvd 67
Generating events
We'll modify the validation return type to be:
WriterT[Result, List[ReservationEvent], A]
Which wraps values of type
Result[(List[ReservationEvent], A)]
Itamar Ravid - @itrvd 68
Generating events
Now, when we use the .tupled syntax, we get back:
val writer: WriterT[Result, List[ReservationEvent], Unit] =
(validateDup(reservations, command),
validateGuests(command)).tupled.void
And it actually concatenated the lists for us!
Itamar Ravid - @itrvd 69
Generating events
We can "peel" this using .run:
val writer: WriterT[Result, List[ReservationEvent], Unit] =
(validateDup(reservations, command),
validateGuests(command)).tupled.void
val result: Result[(List[ReservationEvent], Unit)] =
writer.run
Itamar Ravid - @itrvd 70
Generating events
Or, if we only want the log:
val result: Result[List[ReservationEvent]] =
writer.written
Itamar Ravid - @itrvd 71
Generating events
So let's give this new type a name:
type EventsAnd[A] = WriterT[Result, List[ReservationEvent], A]
Itamar Ravid - @itrvd 72
Actually writing those functions
Here's an example of how one of those functions would look like:
def validateGuests(command: Create): EventsAnd[Unit] =
if (command.guests <= 0)
WriterT.liftF("Non-positive guests".asLeft[Unit])
else
WriterT.putT(().asRight[Error])(List(ReservationCreated(command.id)))
Itamar Ravid - @itrvd 73
Generating events - summary
We're done! Our functions now validate and generate events:
def validateGuests(command: Create): EventsAnd[Unit]
def validateDup(reservations: Reservations,
command: Create): EventsAnd[Unit]
val result = (validateGuests(c), validateDup(s, c))
.tupled.void
Itamar Ravid - @itrvd 74
Generating events - summary
We're done! Our functions now validate and generate events:
def validateGuests(command: Create): EventsAnd[Unit]
def validateDup(reservations: Reservations,
command: Create): EventsAnd[Unit]
val result = (validateGuests(c), validateDup(s, c))
.tupled.void
Of course, if validation fails - everything still fails.
Itamar Ravid - @itrvd 75
A few side notes on WriterT
โ€ข WriterT is also a Monad and an Applicative.
โ€ข Lots of combinators - check out the docs!
โ€ข WriterT can be quite allocation-heavy.
We'll touch a possible solution towards the end.
Itamar Ravid - @itrvd 76
Itamar Ravid - @itrvd 77
State mutation
Our function doesn't do anything currently:
def processCommand(reservations: Reservations,
command: ReservationCommand): EventsAnd[Unit]
Itamar Ravid - @itrvd 78
State mutation
It's pretty awesome that we can reason about it just by looking at
the type:
(Reservations, Command) => WriterT[Result, List[Event], Unit]
Nothing else happens. WYSIWYG.
Itamar Ravid - @itrvd 79
State mutation
The path of least resistance is to return a new state:
def processCommand(reservations: Reservations,
command: ReservationCommand): (Reservations, EventsAnd[Unit])
Itamar Ravid - @itrvd 80
State mutation
The path of least resistance is to return a new state:
def processCommand(reservations: Reservations,
command: ReservationCommand): (Reservations, EventsAnd[Unit])
We're not saying anything about whether it was modi๏ฌed.
Itamar Ravid - @itrvd 81
State mutation
However, we want to guarantee that nothing happens if
validation fails.
def processCommand(reservations: Reservations,
command: ReservationCommand): EventsAnd[Reservations]
If validation is ๏ฌne, we get a new state.
If it fails, we keep the previous state.
Itamar Ravid - @itrvd 82
State mutation
But - how do we reconcile the two resulting states?
def validateGuests(s: Reservations, c: Create): EventsAnd[Reservations]
def validateDup(s: Reservations, c: Create): EventsAnd[Reservations]
val result: EventsAnd[(Reservations, Reservations)] =
(validateGuests(s, c),
validateDup(s, c)).tupled
Itamar Ravid - @itrvd 83
State mutation
Ideally, we'd just carry the state from function to function:
Itamar Ravid - @itrvd 84
State mutation
Let's modify the functions slightly:
def validateGuests(s: Reservations, c: Create): EventsAnd[Reservations]
Itamar Ravid - @itrvd 85
State mutation
Let's modify the functions slightly:
def validateGuests(s: Reservations, c: Create): EventsAnd[Reservations]
def validateGuests(c: Create): Reservations => EventsAnd[Reservations]
Itamar Ravid - @itrvd 86
State mutation
Functional programming saves the day again- this is StateT:
case class StateT[F[_], State, Value](run: State => F[(State, Value)])
Itamar Ravid - @itrvd 87
State mutation
We add yet another type alias on top:
type CommandProcessor[A] = StateT[EventsAnd, Reservations, A]
def validateGuests(c: Create): CommandProcessor[Unit]
def validateDup(c: Create): CommandProcessor[Unit]
Itamar Ravid - @itrvd 88
State mutation
We can now compose our command processors:
def validateGuests(c: Create): CommandProcessor[Unit]
def validateDup(c: Create): CommandProcessor[Unit]
val resultProcessor: CommandProcessor[Unit] =
(validateGuests(c),
validateDup(c)).tupled.void
But where did our initial state disappear to?
Itamar Ravid - @itrvd 89
State mutation
We haven't run anything yet! Let's run the processor:
val result: EventsAnd[(Reservations, Unit)] = resultProcessor.run(state)
This does a lot of stuff!
Itamar Ravid - @itrvd 90
State mutation
Here's how we actually write one of these functions:
def validateGuests(command: Create): CommandProcessor[Unit] =
if (command.guests <= 0)
StateT.liftF(WriterT.liftF("Non-positive guests".asLeft[Unit]))
else
for {
state <- StateT.get[EventsAnd, Reservations]
events = generateEventsSomehow(state)
_ <- StateT.liftF(WriterT.tell[Result, List[ReservationEvent]](events))
newState = generateNewStateSomehow(reservations, event)
_ <- StateT.set[EventsAnd, Reservations](newState)
} yield ()
Itamar Ravid - @itrvd 91
State mutation
Here's how we actually write one of
these functions:
def validateGuests(command: Create): CommandProcessor[Unit] =
if (command.guests <= 0)
StateT.liftF(WriterT.liftF("Non-positive guests".asLeft[Unit]))
else
for {
state <- StateT.get[EventsAnd, Reservations]
events = generateEventsSomehow(state)
_ <- StateT.liftF(WriterT.tell[Result, List[ReservationEvent]](events))
newState = generateNewStateSomehow(reservations, event)
_ <- StateT.set[EventsAnd, Reservations](newState)
} yield ()
Itamar Ravid - @itrvd 92
Summary
โ€ข I bet you're loving the boilerplate by now ;-)
It gets worse as you add more layers!
โ€ข Don't worry though! This is solvable.
โ€ข As before, StateT is also a Monad and an Applicative.
Itamar Ravid - @itrvd 93
Recap
We now have a pretty powerful type as a building block:
type Result[A] = Either[Error, A]
type EventsAnd[A] = WriterT[Result, List[ReservationEvent], A]
type CommandProcessor[A] = StateT[EventsAnd, Reservations, A]
Itamar Ravid - @itrvd 94
Recap
If we expand everything, we get:
type CommandProcessor[A] =
Reservations => Either[Error, (List[Event], (Reservations, A))]
Itamar Ravid - @itrvd 95
Itamar Ravid - @itrvd 96
Itamar Ravid - @itrvd 97
Itamar Ravid - @itrvd 98
Itamar Ravid - @itrvd 99
Is that it?
type Result[A] = Either[Error, A]
type EventsAnd[A] = WriterT[Result, List[ReservationEvent], A]
type CommandProcessor[A] = StateT[EventsAnd, Reservations, A]
The command processor can return values, signal failures, log
events, and accumulate state.
Itamar Ravid - @itrvd 100
Is that it?
type Result[A] = Either[Error, A]
type EventsAnd[A] = WriterT[Result, List[ReservationEvent], A]
type CommandProcessor[A] = StateT[EventsAnd, Reservations, A]
The command processor can return values, signal failures, log
events, and accumulate state.
No magic! These are just functions!
Itamar Ravid - @itrvd 101
Fitting this into an application
We still have some work to do:
โ€ข we need to hook this up to a source of commands,
โ€ข and we actually need to persist the state and events.
Itamar Ravid - @itrvd 102
Fitting this into an application
Where can commands come from?
Treating these as in๏ฌnite streams makes our life easier.
Itamar Ravid - @itrvd 103
Streams
We'll use fs2 for our examples.
Akka Streams can de๏ฌnitely work too! Check out the slides
when I publish them.
Itamar Ravid - @itrvd 104
fs2
fs2 is based on one type:
Stream[Effect[_], Element]
A possibly in๏ฌnite stream of Element, with effect Effect.
Effect can be cats IO, scalaz IO, Monix Task, etc.
Itamar Ravid - @itrvd 105
fs2
fs2 is based on one type:
Stream[Effect[_], Element]
A possibly in๏ฌnite stream of Element, with effect Effect.
Itamar Ravid - @itrvd 106
fs2
To run our command processor, we can use mapAccumulate:
class Stream[F[_], Element] {
def mapAccumulate[State, Out](init: State)
(f: (State, Element) => (State, Out)): Stream[F, (State, Out)]
}
Itamar Ravid - @itrvd 107
fs2
To run our command processor, we can use mapAccumulate:
class Stream[F[_], Element] {
def mapAccumulate[State, Out](init: State)
(f: (State, Element) => (State, Out)): Stream[F, (State, Out)]
}
We'll set:
โ€ข Element = Command, State = Reservations
โ€ข Out = (Reservations, List[ReservationEvent])
Itamar Ravid - @itrvd 108
fs2
def process(commands: Stream[IO, Command], initial: Reservations)
: Stream[IO, (Reservations, List[ReservationEvent])] =
commands.mapAccumulate(initial) { (state, command) =>
processCommand(command).run(state) match {
case Left(error) => (state, (state, Nil))
case Right(nextState, (events, _)) => (nextState, (nextState, events))
}
}
Itamar Ravid - @itrvd 109
Itamar Ravid - @itrvd 110
Itamar Ravid - @itrvd 111
Persisting events and state
def persist(reservations: Reservations): IO[Unit]
def produce(events: List[ReservationEvent]): IO[Unit]
stream.evalMap {
case (state, events) =>
(persist(state), produce(events))
.tupled
.void
}
Itamar Ravid - @itrvd 112
Where to go from here
โ€ข The solution to monad transformer overhead: ๏ฌnally tagless
โ€ข Keep the effect abstract, interpret into Task+IORef.
โ€ข Check out the links on next slide.
โ€ข You can also hire me and I'll help you make it work ;-)
Itamar Ravid - @itrvd 113
Where to go from here
Resources:
โ€ข Ben Stopford, Designing Event-Driven Systems:
https://www.con๏ฌ‚uent.io/designing-event-driven-systems
โ€ข Martin Kleppman's blog:
https://martin.kleppmann.com
โ€ข The FS2 guide:
https://functional-streams-for-scala.github.io/fs2/
โ€ข Finally tagless and Free:
https://softwaremill.com/free-tagless-compared-how-not-to-commit-to-monad-too-early/
Itamar Ravid - @itrvd 114
Questions?
Itamar Ravid - @itrvd 115
Thank you!
Itamar Ravid - @itrvd 116
BonusItamar Ravid - @itrvd 117
Digression: Folds
If you squint, our function looks like a foldLeft:
def foldLeft[S, A](init: S)
(f: (S, A ) => S ): S
def processCommand: (Reservations, ReservationCommand) => Reservations
Itamar Ravid - @itrvd 118
Digression: Folds
There's also an interesting variant of foldLeft when the result
has an effect:
def foldLeftM[S, A](init: S)(f: (S, A) => IO[S]): IO[S]
Itamar Ravid - @itrvd 119
Digression: Folds
Since EventsAnd is a Monad, we can use foldLeftM:
val commands: List[ReservationCommand]
val resultState: EventsAnd[Reservations] =
commands.foldLeftM(Reservations.empty)(processCommand)
Any failed validation will halt the processing,
and all the events would be accumulated.
Itamar Ravid - @itrvd 120
Itamar Ravid - @itrvd 121
Digression: Folds
Another interesting type of fold is a scan:
def scanLeft[S, A](init: S)(f: (S, A) => S): List[S]
Looks exactly like a fold, but you get a list of intermediate states.
Itamar Ravid - @itrvd 122
Digression: Folds
Scanning commands would get us a list of intermediate
Reservations:
val commands: List[ReservationCommand]
val states = commands.scanLeft(Reservations.empty)(processCommand)
// List(reservations1, reservations2, ...)
Itamar Ravid - @itrvd 123
Traversals
What's the shortest way to make processCommand work
on a List[ReservationCommand]?
def processCommand(command: ReservationCommand): CommandProcessor[Unit]
Itamar Ravid - @itrvd 124
Traversals
Let's try mapping.
val cmds: List[ReservationCommand]
val mapped: List[CommandProcessor[Unit]] = cmds.map(processCommand)
We get back a list of functions; not very helpful.
Itamar Ravid - @itrvd 125
Traversals
The operation we're looking for is an effectful map:
Itamar Ravid - @itrvd 126
Traversals
Lucky for us, we have the sequence function:
val cmds: List[ReservationCommand]
val mapped: List[CommandProcessor[Unit]] = cmds.map(processCommand)
val sequenced: CommandProcessor[List[Unit]] = mapped.sequence
So now we get one giant processing function that runs
everything!
Itamar Ravid - @itrvd 127
Traversals
As the title gave away, this can be expressed as:
val traversed: CommandProcessor[List[Unit]] =
cmds.traverse(processCommand)
And we can actually run it:
traversed.run(initState) match {
case Left(_) =>
// log the error
case Right(resultState, (events, _)) =>
// persist the events and state
}
Itamar Ravid - @itrvd 128
Traversals
This is our Traversable typeclass:
trait Traversable[F[_]] extends Functor[F]
with Foldable[F] {
def traverse[G[_]: Applicative, A, B](fa: F[A])
(f: A => G[B]): G[F[B]]
}
Itamar Ravid - @itrvd 129
Traversals
If you line traverse up with map, the effectful map analogy is
clearer:
def map [A, B](fa: F[A])(f: A => B ): F[B]
def traverse[G[_], A, B](fa: F[A])(f: A => G[B]): G[F[B]]
Itamar Ravid - @itrvd 130
Akka Streams
With Akka Streams, we can use the scan operator to process
commands:
class Source[Element] {
def scan[State](zero: State)(f: (State, Element) => State): Source[State]
}
If we set:
โ€ข Element = ReservationCommand
โ€ข State = (Reservations, List[ReservationEvent])
Itamar Ravid - @itrvd 131
Akka Streams
def commandProcessFlow(initial: Reservations):
Flow[ReservationCommand, (Reservations, List[ReservationEvent]), NotUsed] =
Flow[ReservationCommand]
.scan((initial, List[ReservationEvent]())) {
case ((state, _), command) =>
processCommand(command).run(state) match {
case Left(error) => (state, Nil)
case Right(nextState, (events, _)) => (nextState, events)
}
}
Itamar Ravid - @itrvd 132

More Related Content

Similar to Traversals and Scans and CQRS, oh my!

Geek Sync | Rewriting Bad SQL Code 101
Geek Sync | Rewriting Bad SQL Code 101Geek Sync | Rewriting Bad SQL Code 101
Geek Sync | Rewriting Bad SQL Code 101IDERA Software
ย 
Micro-service architectures with Gilmour
Micro-service architectures with GilmourMicro-service architectures with Gilmour
Micro-service architectures with GilmourAditya Godbole
ย 
The Challenge of Bringing FEZ to PlayStation Platforms
The Challenge of Bringing FEZ to PlayStation PlatformsThe Challenge of Bringing FEZ to PlayStation Platforms
The Challenge of Bringing FEZ to PlayStation PlatformsMiguel Angel Horna
ย 
Profiling Mondrian MDX Requests in a Production Environment
Profiling Mondrian MDX Requests in a Production EnvironmentProfiling Mondrian MDX Requests in a Production Environment
Profiling Mondrian MDX Requests in a Production EnvironmentRaimonds Simanovskis
ย 
The System of Automatic Searching for Vulnerabilities or how to use Taint Ana...
The System of Automatic Searching for Vulnerabilities or how to use Taint Ana...The System of Automatic Searching for Vulnerabilities or how to use Taint Ana...
The System of Automatic Searching for Vulnerabilities or how to use Taint Ana...Positive Hack Days
ย 
Reduce SRE Stress: Minimizing Service Downtime with Grafana, InfluxDB and Tel...
Reduce SRE Stress: Minimizing Service Downtime with Grafana, InfluxDB and Tel...Reduce SRE Stress: Minimizing Service Downtime with Grafana, InfluxDB and Tel...
Reduce SRE Stress: Minimizing Service Downtime with Grafana, InfluxDB and Tel...InfluxData
ย 
Session 6 sv_randomization
Session 6 sv_randomizationSession 6 sv_randomization
Session 6 sv_randomizationNirav Desai
ย 
MySQL under the siege
MySQL under the siegeMySQL under the siege
MySQL under the siegeSource Ministry
ย 
Jay Clifford [InfluxData] | Tips & Tricks for Analyzing IIoT in Real-Time | I...
Jay Clifford [InfluxData] | Tips & Tricks for Analyzing IIoT in Real-Time | I...Jay Clifford [InfluxData] | Tips & Tricks for Analyzing IIoT in Real-Time | I...
Jay Clifford [InfluxData] | Tips & Tricks for Analyzing IIoT in Real-Time | I...InfluxData
ย 
Dino2 - the Amazing Evolution of the VA Smalltalk Virtual Machine
Dino2 - the Amazing Evolution of the VA Smalltalk Virtual MachineDino2 - the Amazing Evolution of the VA Smalltalk Virtual Machine
Dino2 - the Amazing Evolution of the VA Smalltalk Virtual MachineESUG
ย 
Trends and development practices in Serverless architectures
Trends and development practices in Serverless architecturesTrends and development practices in Serverless architectures
Trends and development practices in Serverless architecturesDiUS
ย 
Vertical Recommendation Using Collaborative Filtering
Vertical Recommendation Using Collaborative FilteringVertical Recommendation Using Collaborative Filtering
Vertical Recommendation Using Collaborative Filteringgorass
ย 
Run Node Run
Run Node RunRun Node Run
Run Node RunKevin Swiber
ย 
JCon Live 2023 - Lice coding some integration problems
JCon Live 2023 - Lice coding some integration problemsJCon Live 2023 - Lice coding some integration problems
JCon Live 2023 - Lice coding some integration problemsBernd Ruecker
ย 
2Bytesprog2 course_2014_c1_sets
2Bytesprog2 course_2014_c1_sets2Bytesprog2 course_2014_c1_sets
2Bytesprog2 course_2014_c1_setskinan keshkeh
ย 
2Bytesprog2 course_2014_c2_records
2Bytesprog2 course_2014_c2_records2Bytesprog2 course_2014_c2_records
2Bytesprog2 course_2014_c2_recordskinan keshkeh
ย 
So I Wrote a Manifest
So I Wrote a ManifestSo I Wrote a Manifest
So I Wrote a ManifestPuppet
ย 
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018 Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018 Codemotion
ย 
SBJUG - Building Beautiful Batch Jobs
SBJUG - Building Beautiful Batch JobsSBJUG - Building Beautiful Batch Jobs
SBJUG - Building Beautiful Batch Jobsstephenbhadran
ย 
Composable and streamable Play apps
Composable and streamable Play appsComposable and streamable Play apps
Composable and streamable Play appsYevgeniy Brikman
ย 

Similar to Traversals and Scans and CQRS, oh my! (20)

Geek Sync | Rewriting Bad SQL Code 101
Geek Sync | Rewriting Bad SQL Code 101Geek Sync | Rewriting Bad SQL Code 101
Geek Sync | Rewriting Bad SQL Code 101
ย 
Micro-service architectures with Gilmour
Micro-service architectures with GilmourMicro-service architectures with Gilmour
Micro-service architectures with Gilmour
ย 
The Challenge of Bringing FEZ to PlayStation Platforms
The Challenge of Bringing FEZ to PlayStation PlatformsThe Challenge of Bringing FEZ to PlayStation Platforms
The Challenge of Bringing FEZ to PlayStation Platforms
ย 
Profiling Mondrian MDX Requests in a Production Environment
Profiling Mondrian MDX Requests in a Production EnvironmentProfiling Mondrian MDX Requests in a Production Environment
Profiling Mondrian MDX Requests in a Production Environment
ย 
The System of Automatic Searching for Vulnerabilities or how to use Taint Ana...
The System of Automatic Searching for Vulnerabilities or how to use Taint Ana...The System of Automatic Searching for Vulnerabilities or how to use Taint Ana...
The System of Automatic Searching for Vulnerabilities or how to use Taint Ana...
ย 
Reduce SRE Stress: Minimizing Service Downtime with Grafana, InfluxDB and Tel...
Reduce SRE Stress: Minimizing Service Downtime with Grafana, InfluxDB and Tel...Reduce SRE Stress: Minimizing Service Downtime with Grafana, InfluxDB and Tel...
Reduce SRE Stress: Minimizing Service Downtime with Grafana, InfluxDB and Tel...
ย 
Session 6 sv_randomization
Session 6 sv_randomizationSession 6 sv_randomization
Session 6 sv_randomization
ย 
MySQL under the siege
MySQL under the siegeMySQL under the siege
MySQL under the siege
ย 
Jay Clifford [InfluxData] | Tips & Tricks for Analyzing IIoT in Real-Time | I...
Jay Clifford [InfluxData] | Tips & Tricks for Analyzing IIoT in Real-Time | I...Jay Clifford [InfluxData] | Tips & Tricks for Analyzing IIoT in Real-Time | I...
Jay Clifford [InfluxData] | Tips & Tricks for Analyzing IIoT in Real-Time | I...
ย 
Dino2 - the Amazing Evolution of the VA Smalltalk Virtual Machine
Dino2 - the Amazing Evolution of the VA Smalltalk Virtual MachineDino2 - the Amazing Evolution of the VA Smalltalk Virtual Machine
Dino2 - the Amazing Evolution of the VA Smalltalk Virtual Machine
ย 
Trends and development practices in Serverless architectures
Trends and development practices in Serverless architecturesTrends and development practices in Serverless architectures
Trends and development practices in Serverless architectures
ย 
Vertical Recommendation Using Collaborative Filtering
Vertical Recommendation Using Collaborative FilteringVertical Recommendation Using Collaborative Filtering
Vertical Recommendation Using Collaborative Filtering
ย 
Run Node Run
Run Node RunRun Node Run
Run Node Run
ย 
JCon Live 2023 - Lice coding some integration problems
JCon Live 2023 - Lice coding some integration problemsJCon Live 2023 - Lice coding some integration problems
JCon Live 2023 - Lice coding some integration problems
ย 
2Bytesprog2 course_2014_c1_sets
2Bytesprog2 course_2014_c1_sets2Bytesprog2 course_2014_c1_sets
2Bytesprog2 course_2014_c1_sets
ย 
2Bytesprog2 course_2014_c2_records
2Bytesprog2 course_2014_c2_records2Bytesprog2 course_2014_c2_records
2Bytesprog2 course_2014_c2_records
ย 
So I Wrote a Manifest
So I Wrote a ManifestSo I Wrote a Manifest
So I Wrote a Manifest
ย 
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018 Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
Enrique Amodeo | Graphql + Microservices = Win! | Codemotion Madrid 2018
ย 
SBJUG - Building Beautiful Batch Jobs
SBJUG - Building Beautiful Batch JobsSBJUG - Building Beautiful Batch Jobs
SBJUG - Building Beautiful Batch Jobs
ย 
Composable and streamable Play apps
Composable and streamable Play appsComposable and streamable Play apps
Composable and streamable Play apps
ย 

Recently uploaded

Shapes for Sharing between Graph Data Spacesย - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spacesย - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spacesย - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spacesย - and Epistemic Querying of RDF-...Steffen Staab
ย 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
ย 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
ย 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
ย 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlanโ€™s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlanโ€™s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlanโ€™s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlanโ€™s ...OnePlan Solutions
ย 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
ย 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
ย 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerThousandEyes
ย 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
ย 
call girls in Vaishali (Ghaziabad) ๐Ÿ” >เผ’8448380779 ๐Ÿ” genuine Escort Service ๐Ÿ”โœ”๏ธโœ”๏ธ
call girls in Vaishali (Ghaziabad) ๐Ÿ” >เผ’8448380779 ๐Ÿ” genuine Escort Service ๐Ÿ”โœ”๏ธโœ”๏ธcall girls in Vaishali (Ghaziabad) ๐Ÿ” >เผ’8448380779 ๐Ÿ” genuine Escort Service ๐Ÿ”โœ”๏ธโœ”๏ธ
call girls in Vaishali (Ghaziabad) ๐Ÿ” >เผ’8448380779 ๐Ÿ” genuine Escort Service ๐Ÿ”โœ”๏ธโœ”๏ธDelhi Call girls
ย 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfWilly Marroquin (WillyDevNET)
ย 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
ย 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
ย 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
ย 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AIABDERRAOUF MEHENNI
ย 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...Health
ย 
CHEAP Call Girls in Pushp Vihar (-DELHI )๐Ÿ” 9953056974๐Ÿ”(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )๐Ÿ” 9953056974๐Ÿ”(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )๐Ÿ” 9953056974๐Ÿ”(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )๐Ÿ” 9953056974๐Ÿ”(=)/CALL GIRLS SERVICE9953056974 Low Rate Call Girls In Saket, Delhi NCR
ย 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsAndolasoft Inc
ย 
CALL ON โžฅ8923113531 ๐Ÿ”Call Girls Badshah Nagar Lucknow best Female service
CALL ON โžฅ8923113531 ๐Ÿ”Call Girls Badshah Nagar Lucknow best Female serviceCALL ON โžฅ8923113531 ๐Ÿ”Call Girls Badshah Nagar Lucknow best Female service
CALL ON โžฅ8923113531 ๐Ÿ”Call Girls Badshah Nagar Lucknow best Female serviceanilsa9823
ย 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
ย 

Recently uploaded (20)

Shapes for Sharing between Graph Data Spacesย - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spacesย - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spacesย - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spacesย - and Epistemic Querying of RDF-...
ย 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
ย 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
ย 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
ย 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlanโ€™s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlanโ€™s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlanโ€™s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlanโ€™s ...
ย 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
ย 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
ย 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
ย 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
ย 
call girls in Vaishali (Ghaziabad) ๐Ÿ” >เผ’8448380779 ๐Ÿ” genuine Escort Service ๐Ÿ”โœ”๏ธโœ”๏ธ
call girls in Vaishali (Ghaziabad) ๐Ÿ” >เผ’8448380779 ๐Ÿ” genuine Escort Service ๐Ÿ”โœ”๏ธโœ”๏ธcall girls in Vaishali (Ghaziabad) ๐Ÿ” >เผ’8448380779 ๐Ÿ” genuine Escort Service ๐Ÿ”โœ”๏ธโœ”๏ธ
call girls in Vaishali (Ghaziabad) ๐Ÿ” >เผ’8448380779 ๐Ÿ” genuine Escort Service ๐Ÿ”โœ”๏ธโœ”๏ธ
ย 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
ย 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
ย 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
ย 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
ย 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
ย 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
ย 
CHEAP Call Girls in Pushp Vihar (-DELHI )๐Ÿ” 9953056974๐Ÿ”(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )๐Ÿ” 9953056974๐Ÿ”(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )๐Ÿ” 9953056974๐Ÿ”(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )๐Ÿ” 9953056974๐Ÿ”(=)/CALL GIRLS SERVICE
ย 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
ย 
CALL ON โžฅ8923113531 ๐Ÿ”Call Girls Badshah Nagar Lucknow best Female service
CALL ON โžฅ8923113531 ๐Ÿ”Call Girls Badshah Nagar Lucknow best Female serviceCALL ON โžฅ8923113531 ๐Ÿ”Call Girls Badshah Nagar Lucknow best Female service
CALL ON โžฅ8923113531 ๐Ÿ”Call Girls Badshah Nagar Lucknow best Female service
ย 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
ย 

Traversals and Scans and CQRS, oh my!

  • 1. Itamar Ravid - @itrvd 1
  • 2. About me โ€ข Freelance software developer โ€ข Doing functional programming, microservices, DevOps โ€ข Co-organize the Underscore Scala meetup โ€ข Hit me up if you want to work together! Itamar Ravid - @itrvd 2
  • 3. Agenda โ€ข Why Event Sourcing and CQRS are worth your time โ€ข How we can use functions to implement them โ€ข How abstractions from FP help us โ€ข (shortly) Hooking this up to a stream Itamar Ravid - @itrvd 3
  • 5. Itamar Ravid - @itrvd 5
  • 6. Itamar Ravid - @itrvd 6
  • 7. Itamar Ravid - @itrvd 7
  • 8. Itamar Ravid - @itrvd 8
  • 9. Itamar Ravid - @itrvd 9
  • 10. Itamar Ravid - @itrvd 10
  • 11. Itamar Ravid - @itrvd 11
  • 12. Itamar Ravid - @itrvd 12
  • 13. Itamar Ravid - @itrvd 13
  • 14. That's not the only downside! โ€ข No determinism โ€ข Lowly available โ€ข High coupling, barely autonomous Itamar Ravid - @itrvd 14
  • 15. What can we do to ๏ฌx this? Itamar Ravid - @itrvd 15
  • 16. Itamar Ravid - @itrvd 16
  • 17. Itamar Ravid - @itrvd 17
  • 18. Itamar Ravid - @itrvd 18
  • 19. Itamar Ravid - @itrvd 19
  • 20. Itamar Ravid - @itrvd 20
  • 21. Focus on behaviors. The objects will follow. Itamar Ravid - @itrvd 21
  • 22. Itamar Ravid - @itrvd 22
  • 23. Itamar Ravid - @itrvd 23
  • 24. Itamar Ravid - @itrvd 24
  • 25. Responding to queries Itamar Ravid - @itrvd 25
  • 26. Responding to queries Itamar Ravid - @itrvd 26
  • 28. CQRS: Command/Query Responsibility Separation We store the events in an append only stream. We handle reads using a specialized store, derived from the events. Now you know CQRS! Itamar Ravid - @itrvd 28
  • 29. What do we gain? โ€ข Faster response time โ€ข Availability โ€ข Determinism, take your debugging home โ€ข ! Event-sourced services are like pure functions! Itamar Ravid - @itrvd 29
  • 30. And speaking of pure functions Let's see some code! The examples ahead assume: import cats._, cats.data._, cats.implicits._ Itamar Ravid - @itrvd 30
  • 31. Zoom in We'll now focus on the reservation service. Let's event-storm this! What commands/events are we going to use? Itamar Ravid - @itrvd 31
  • 32. Commands We might be asked to: โ€ข Create a reservation โ€ข Add a person to a reservation โ€ข Cancel a reservation Itamar Ravid - @itrvd 32
  • 33. Events And these will cause the: โ€ข Reservation to be created โ€ข Reservation to be updated โ€ข Reservation to be canceled Itamar Ravid - @itrvd 33
  • 34. Command De๏ฌnitions I promised some code, right? sealed trait ReservationCommand case class Create(roomId: String, guests: Int) extends ReservationCommand case class ModifyGuests(reservationId: String, amount: Int) extends ReservationCommand case class Cancel(reservationId: String) extends ReservationCommand Itamar Ravid - @itrvd 34
  • 35. Event De๏ฌnitions And we have the corresponding events: sealed trait ReservationEvent case class Created(id: String, roomId: String, guests: Int) extends ReservationEvent case class Updated(id: String, roomId: String, guests: Int) extends ReservationEvent case class Canceled(id: String, roomId: String, guests: Int) extends ReservationEvent Itamar Ravid - @itrvd 35
  • 36. Itamar Ravid - @itrvd 36
  • 37. Itamar Ravid - @itrvd 37
  • 38. Itamar Ravid - @itrvd 38
  • 39. Itamar Ravid - @itrvd 39
  • 40. Itamar Ravid - @itrvd 40
  • 41. Itamar Ravid - @itrvd 41
  • 42. Breakdown So technically, we need: 1. Validation of commands 2. State representation, state mutation 3. Event generation 4. Persistence Itamar Ravid - @itrvd 42
  • 43. Itamar Ravid - @itrvd 43
  • 44. Itamar Ravid - @itrvd 44
  • 45. Validation So we want a function, right? def processCommand(command: ReservationCommand): ??? Itamar Ravid - @itrvd 45
  • 46. Validation So we want a function, right? def processCommand(command: ReservationCommand): Either[Error, Unit] Itamar Ravid - @itrvd 46
  • 47. Validation So we want a function, right? type Error = String type Result[A] = Either[Error, A] def processCommand(command: ReservationCommand): Result[Unit] Itamar Ravid - @itrvd 47
  • 48. Validation Let's start with simple validation: the number of guests should be positive. def validateGuests(command: Create): Result[Unit] = if (command.guests <= 0) Left("Non-positive guests") else Right(()) Itamar Ravid - @itrvd 48
  • 49. Stateful validation Next, we want a stateful validation - no duplicate reservations. Itamar Ravid - @itrvd 49
  • 50. Stateful validation Next, we want a stateful validation - no duplicate reservations. To do that, we need some sort of state: case class Reservation(id: String) trait Reservations { def byRoomId(id: String): Option[Reservation] } Itamar Ravid - @itrvd 50
  • 51. Validation And here's our validation: def validateDup(reservations: Reservations, command: Create): Result[Unit] = reservations.byRoomId(command.roomId) match { case Some(_) => Left("Duplicate reservation") case None => Right(()) } Itamar Ravid - @itrvd 51
  • 52. Validation - continued Our command processing now looks like this: def processCommand(reservations: Reservations, command: ReservationCommand): Result[Unit] = command match { case create: Create => (validateGuests(create), validateDup(create, reservations)) .tupled.void // case modifyGuests, case cancelReservation, etc. } Itamar Ravid - @itrvd 52
  • 53. Validation - continued What's that (a, b).tupled function? This will combine the result of both validations into a single value: val guestValidation: Result[Unit] = validateGuests(create) val dupValidation: Result[Unit] = validateDup(create, reservations) val tupled: Result[(Unit, Unit)] = (guestValidation, dupValidation).tupled Itamar Ravid - @itrvd 53
  • 54. Validation - continued What's that (a, b).tupled function? This will combine the result of both validations into a single value: val guestValidation: Result[Unit] = validateGuests(create) val dupValidation: Result[Unit] = validateDup(create, reservations) val tupled: Result[(Unit, Unit)] = (guestValidation, dupValidation).tupled If one of them fails - both fails too. Itamar Ravid - @itrvd 54
  • 55. Validation - continued What else can we say about Result? Itamar Ravid - @itrvd 55
  • 56. Validation - continued What else can we say about Result? It's an Applicative - so we can use tupled. Itamar Ravid - @itrvd 56
  • 57. Validation - continued What else can we say about Result? It's also a Monad, so we can use for comprehensions: for { a <- validate1(cmd) b <- validate2(cmd, a) } yield f(a, b) Itamar Ravid - @itrvd 57
  • 58. Validation - continued A few side notes: โ€ข Our validation unfortunately short circuits. We don't see all errors. Check out cats.data.Validated for a solution. โ€ข Applicative s are essential for joining independent computations. De๏ฌnitely read up on them! Check out the mapN, *>, <* combinators. Itamar Ravid - @itrvd 58
  • 59. Validation - summary This is all I'm going to say about validation! โ€ข Write an Either returning function โ€ข Give it what it needs to compute โ€ข Combine with the Applicative combinators โ€ข Pro๏ฌt Validated! Itamar Ravid - @itrvd 59
  • 60. Itamar Ravid - @itrvd 60
  • 61. Generating events The path of least resistance is another function for generating: def events(reservations: Reservations, command: ReservationCommand): List[ReservationEvent] Itamar Ravid - @itrvd 61
  • 62. Generating events We return the events inside Result: def processCommand(reservations: Reservations, command: ReservationCommand) : Result[List[ReservationEvent]] = command match { case create: Create => (validateGuests(create), validateDup(create, reservations)) .tupled .map(_ => events(reservations, commands)) } Since we're using List, we can output 0, 1 or many events. Itamar Ravid - @itrvd 62
  • 63. Generating events This is great if we can separate validation and event generation. Itamar Ravid - @itrvd 63
  • 64. Generating events This is great if we can separate validation and event generation. This isn't always possible! Itamar Ravid - @itrvd 64
  • 65. Generating events This is great if we can separate validation and event generation. This isn't always possible! We'll upgrade the two functions with event generation. Itamar Ravid - @itrvd 65
  • 67. Generating events There's a cool data type called WriterT we can use here: case class WriterT[F[_], Log, Value](run: F[(Log, Value)]) Itamar Ravid - @itrvd 67
  • 68. Generating events We'll modify the validation return type to be: WriterT[Result, List[ReservationEvent], A] Which wraps values of type Result[(List[ReservationEvent], A)] Itamar Ravid - @itrvd 68
  • 69. Generating events Now, when we use the .tupled syntax, we get back: val writer: WriterT[Result, List[ReservationEvent], Unit] = (validateDup(reservations, command), validateGuests(command)).tupled.void And it actually concatenated the lists for us! Itamar Ravid - @itrvd 69
  • 70. Generating events We can "peel" this using .run: val writer: WriterT[Result, List[ReservationEvent], Unit] = (validateDup(reservations, command), validateGuests(command)).tupled.void val result: Result[(List[ReservationEvent], Unit)] = writer.run Itamar Ravid - @itrvd 70
  • 71. Generating events Or, if we only want the log: val result: Result[List[ReservationEvent]] = writer.written Itamar Ravid - @itrvd 71
  • 72. Generating events So let's give this new type a name: type EventsAnd[A] = WriterT[Result, List[ReservationEvent], A] Itamar Ravid - @itrvd 72
  • 73. Actually writing those functions Here's an example of how one of those functions would look like: def validateGuests(command: Create): EventsAnd[Unit] = if (command.guests <= 0) WriterT.liftF("Non-positive guests".asLeft[Unit]) else WriterT.putT(().asRight[Error])(List(ReservationCreated(command.id))) Itamar Ravid - @itrvd 73
  • 74. Generating events - summary We're done! Our functions now validate and generate events: def validateGuests(command: Create): EventsAnd[Unit] def validateDup(reservations: Reservations, command: Create): EventsAnd[Unit] val result = (validateGuests(c), validateDup(s, c)) .tupled.void Itamar Ravid - @itrvd 74
  • 75. Generating events - summary We're done! Our functions now validate and generate events: def validateGuests(command: Create): EventsAnd[Unit] def validateDup(reservations: Reservations, command: Create): EventsAnd[Unit] val result = (validateGuests(c), validateDup(s, c)) .tupled.void Of course, if validation fails - everything still fails. Itamar Ravid - @itrvd 75
  • 76. A few side notes on WriterT โ€ข WriterT is also a Monad and an Applicative. โ€ข Lots of combinators - check out the docs! โ€ข WriterT can be quite allocation-heavy. We'll touch a possible solution towards the end. Itamar Ravid - @itrvd 76
  • 77. Itamar Ravid - @itrvd 77
  • 78. State mutation Our function doesn't do anything currently: def processCommand(reservations: Reservations, command: ReservationCommand): EventsAnd[Unit] Itamar Ravid - @itrvd 78
  • 79. State mutation It's pretty awesome that we can reason about it just by looking at the type: (Reservations, Command) => WriterT[Result, List[Event], Unit] Nothing else happens. WYSIWYG. Itamar Ravid - @itrvd 79
  • 80. State mutation The path of least resistance is to return a new state: def processCommand(reservations: Reservations, command: ReservationCommand): (Reservations, EventsAnd[Unit]) Itamar Ravid - @itrvd 80
  • 81. State mutation The path of least resistance is to return a new state: def processCommand(reservations: Reservations, command: ReservationCommand): (Reservations, EventsAnd[Unit]) We're not saying anything about whether it was modi๏ฌed. Itamar Ravid - @itrvd 81
  • 82. State mutation However, we want to guarantee that nothing happens if validation fails. def processCommand(reservations: Reservations, command: ReservationCommand): EventsAnd[Reservations] If validation is ๏ฌne, we get a new state. If it fails, we keep the previous state. Itamar Ravid - @itrvd 82
  • 83. State mutation But - how do we reconcile the two resulting states? def validateGuests(s: Reservations, c: Create): EventsAnd[Reservations] def validateDup(s: Reservations, c: Create): EventsAnd[Reservations] val result: EventsAnd[(Reservations, Reservations)] = (validateGuests(s, c), validateDup(s, c)).tupled Itamar Ravid - @itrvd 83
  • 84. State mutation Ideally, we'd just carry the state from function to function: Itamar Ravid - @itrvd 84
  • 85. State mutation Let's modify the functions slightly: def validateGuests(s: Reservations, c: Create): EventsAnd[Reservations] Itamar Ravid - @itrvd 85
  • 86. State mutation Let's modify the functions slightly: def validateGuests(s: Reservations, c: Create): EventsAnd[Reservations] def validateGuests(c: Create): Reservations => EventsAnd[Reservations] Itamar Ravid - @itrvd 86
  • 87. State mutation Functional programming saves the day again- this is StateT: case class StateT[F[_], State, Value](run: State => F[(State, Value)]) Itamar Ravid - @itrvd 87
  • 88. State mutation We add yet another type alias on top: type CommandProcessor[A] = StateT[EventsAnd, Reservations, A] def validateGuests(c: Create): CommandProcessor[Unit] def validateDup(c: Create): CommandProcessor[Unit] Itamar Ravid - @itrvd 88
  • 89. State mutation We can now compose our command processors: def validateGuests(c: Create): CommandProcessor[Unit] def validateDup(c: Create): CommandProcessor[Unit] val resultProcessor: CommandProcessor[Unit] = (validateGuests(c), validateDup(c)).tupled.void But where did our initial state disappear to? Itamar Ravid - @itrvd 89
  • 90. State mutation We haven't run anything yet! Let's run the processor: val result: EventsAnd[(Reservations, Unit)] = resultProcessor.run(state) This does a lot of stuff! Itamar Ravid - @itrvd 90
  • 91. State mutation Here's how we actually write one of these functions: def validateGuests(command: Create): CommandProcessor[Unit] = if (command.guests <= 0) StateT.liftF(WriterT.liftF("Non-positive guests".asLeft[Unit])) else for { state <- StateT.get[EventsAnd, Reservations] events = generateEventsSomehow(state) _ <- StateT.liftF(WriterT.tell[Result, List[ReservationEvent]](events)) newState = generateNewStateSomehow(reservations, event) _ <- StateT.set[EventsAnd, Reservations](newState) } yield () Itamar Ravid - @itrvd 91
  • 92. State mutation Here's how we actually write one of these functions: def validateGuests(command: Create): CommandProcessor[Unit] = if (command.guests <= 0) StateT.liftF(WriterT.liftF("Non-positive guests".asLeft[Unit])) else for { state <- StateT.get[EventsAnd, Reservations] events = generateEventsSomehow(state) _ <- StateT.liftF(WriterT.tell[Result, List[ReservationEvent]](events)) newState = generateNewStateSomehow(reservations, event) _ <- StateT.set[EventsAnd, Reservations](newState) } yield () Itamar Ravid - @itrvd 92
  • 93. Summary โ€ข I bet you're loving the boilerplate by now ;-) It gets worse as you add more layers! โ€ข Don't worry though! This is solvable. โ€ข As before, StateT is also a Monad and an Applicative. Itamar Ravid - @itrvd 93
  • 94. Recap We now have a pretty powerful type as a building block: type Result[A] = Either[Error, A] type EventsAnd[A] = WriterT[Result, List[ReservationEvent], A] type CommandProcessor[A] = StateT[EventsAnd, Reservations, A] Itamar Ravid - @itrvd 94
  • 95. Recap If we expand everything, we get: type CommandProcessor[A] = Reservations => Either[Error, (List[Event], (Reservations, A))] Itamar Ravid - @itrvd 95
  • 96. Itamar Ravid - @itrvd 96
  • 97. Itamar Ravid - @itrvd 97
  • 98. Itamar Ravid - @itrvd 98
  • 99. Itamar Ravid - @itrvd 99
  • 100. Is that it? type Result[A] = Either[Error, A] type EventsAnd[A] = WriterT[Result, List[ReservationEvent], A] type CommandProcessor[A] = StateT[EventsAnd, Reservations, A] The command processor can return values, signal failures, log events, and accumulate state. Itamar Ravid - @itrvd 100
  • 101. Is that it? type Result[A] = Either[Error, A] type EventsAnd[A] = WriterT[Result, List[ReservationEvent], A] type CommandProcessor[A] = StateT[EventsAnd, Reservations, A] The command processor can return values, signal failures, log events, and accumulate state. No magic! These are just functions! Itamar Ravid - @itrvd 101
  • 102. Fitting this into an application We still have some work to do: โ€ข we need to hook this up to a source of commands, โ€ข and we actually need to persist the state and events. Itamar Ravid - @itrvd 102
  • 103. Fitting this into an application Where can commands come from? Treating these as in๏ฌnite streams makes our life easier. Itamar Ravid - @itrvd 103
  • 104. Streams We'll use fs2 for our examples. Akka Streams can de๏ฌnitely work too! Check out the slides when I publish them. Itamar Ravid - @itrvd 104
  • 105. fs2 fs2 is based on one type: Stream[Effect[_], Element] A possibly in๏ฌnite stream of Element, with effect Effect. Effect can be cats IO, scalaz IO, Monix Task, etc. Itamar Ravid - @itrvd 105
  • 106. fs2 fs2 is based on one type: Stream[Effect[_], Element] A possibly in๏ฌnite stream of Element, with effect Effect. Itamar Ravid - @itrvd 106
  • 107. fs2 To run our command processor, we can use mapAccumulate: class Stream[F[_], Element] { def mapAccumulate[State, Out](init: State) (f: (State, Element) => (State, Out)): Stream[F, (State, Out)] } Itamar Ravid - @itrvd 107
  • 108. fs2 To run our command processor, we can use mapAccumulate: class Stream[F[_], Element] { def mapAccumulate[State, Out](init: State) (f: (State, Element) => (State, Out)): Stream[F, (State, Out)] } We'll set: โ€ข Element = Command, State = Reservations โ€ข Out = (Reservations, List[ReservationEvent]) Itamar Ravid - @itrvd 108
  • 109. fs2 def process(commands: Stream[IO, Command], initial: Reservations) : Stream[IO, (Reservations, List[ReservationEvent])] = commands.mapAccumulate(initial) { (state, command) => processCommand(command).run(state) match { case Left(error) => (state, (state, Nil)) case Right(nextState, (events, _)) => (nextState, (nextState, events)) } } Itamar Ravid - @itrvd 109
  • 110. Itamar Ravid - @itrvd 110
  • 111. Itamar Ravid - @itrvd 111
  • 112. Persisting events and state def persist(reservations: Reservations): IO[Unit] def produce(events: List[ReservationEvent]): IO[Unit] stream.evalMap { case (state, events) => (persist(state), produce(events)) .tupled .void } Itamar Ravid - @itrvd 112
  • 113. Where to go from here โ€ข The solution to monad transformer overhead: ๏ฌnally tagless โ€ข Keep the effect abstract, interpret into Task+IORef. โ€ข Check out the links on next slide. โ€ข You can also hire me and I'll help you make it work ;-) Itamar Ravid - @itrvd 113
  • 114. Where to go from here Resources: โ€ข Ben Stopford, Designing Event-Driven Systems: https://www.con๏ฌ‚uent.io/designing-event-driven-systems โ€ข Martin Kleppman's blog: https://martin.kleppmann.com โ€ข The FS2 guide: https://functional-streams-for-scala.github.io/fs2/ โ€ข Finally tagless and Free: https://softwaremill.com/free-tagless-compared-how-not-to-commit-to-monad-too-early/ Itamar Ravid - @itrvd 114
  • 116. Thank you! Itamar Ravid - @itrvd 116
  • 117. BonusItamar Ravid - @itrvd 117
  • 118. Digression: Folds If you squint, our function looks like a foldLeft: def foldLeft[S, A](init: S) (f: (S, A ) => S ): S def processCommand: (Reservations, ReservationCommand) => Reservations Itamar Ravid - @itrvd 118
  • 119. Digression: Folds There's also an interesting variant of foldLeft when the result has an effect: def foldLeftM[S, A](init: S)(f: (S, A) => IO[S]): IO[S] Itamar Ravid - @itrvd 119
  • 120. Digression: Folds Since EventsAnd is a Monad, we can use foldLeftM: val commands: List[ReservationCommand] val resultState: EventsAnd[Reservations] = commands.foldLeftM(Reservations.empty)(processCommand) Any failed validation will halt the processing, and all the events would be accumulated. Itamar Ravid - @itrvd 120
  • 121. Itamar Ravid - @itrvd 121
  • 122. Digression: Folds Another interesting type of fold is a scan: def scanLeft[S, A](init: S)(f: (S, A) => S): List[S] Looks exactly like a fold, but you get a list of intermediate states. Itamar Ravid - @itrvd 122
  • 123. Digression: Folds Scanning commands would get us a list of intermediate Reservations: val commands: List[ReservationCommand] val states = commands.scanLeft(Reservations.empty)(processCommand) // List(reservations1, reservations2, ...) Itamar Ravid - @itrvd 123
  • 124. Traversals What's the shortest way to make processCommand work on a List[ReservationCommand]? def processCommand(command: ReservationCommand): CommandProcessor[Unit] Itamar Ravid - @itrvd 124
  • 125. Traversals Let's try mapping. val cmds: List[ReservationCommand] val mapped: List[CommandProcessor[Unit]] = cmds.map(processCommand) We get back a list of functions; not very helpful. Itamar Ravid - @itrvd 125
  • 126. Traversals The operation we're looking for is an effectful map: Itamar Ravid - @itrvd 126
  • 127. Traversals Lucky for us, we have the sequence function: val cmds: List[ReservationCommand] val mapped: List[CommandProcessor[Unit]] = cmds.map(processCommand) val sequenced: CommandProcessor[List[Unit]] = mapped.sequence So now we get one giant processing function that runs everything! Itamar Ravid - @itrvd 127
  • 128. Traversals As the title gave away, this can be expressed as: val traversed: CommandProcessor[List[Unit]] = cmds.traverse(processCommand) And we can actually run it: traversed.run(initState) match { case Left(_) => // log the error case Right(resultState, (events, _)) => // persist the events and state } Itamar Ravid - @itrvd 128
  • 129. Traversals This is our Traversable typeclass: trait Traversable[F[_]] extends Functor[F] with Foldable[F] { def traverse[G[_]: Applicative, A, B](fa: F[A]) (f: A => G[B]): G[F[B]] } Itamar Ravid - @itrvd 129
  • 130. Traversals If you line traverse up with map, the effectful map analogy is clearer: def map [A, B](fa: F[A])(f: A => B ): F[B] def traverse[G[_], A, B](fa: F[A])(f: A => G[B]): G[F[B]] Itamar Ravid - @itrvd 130
  • 131. Akka Streams With Akka Streams, we can use the scan operator to process commands: class Source[Element] { def scan[State](zero: State)(f: (State, Element) => State): Source[State] } If we set: โ€ข Element = ReservationCommand โ€ข State = (Reservations, List[ReservationEvent]) Itamar Ravid - @itrvd 131
  • 132. Akka Streams def commandProcessFlow(initial: Reservations): Flow[ReservationCommand, (Reservations, List[ReservationEvent]), NotUsed] = Flow[ReservationCommand] .scan((initial, List[ReservationEvent]())) { case ((state, _), command) => processCommand(command).run(state) match { case Left(error) => (state, Nil) case Right(nextState, (events, _)) => (nextState, events) } } Itamar Ravid - @itrvd 132