Aecor
Purely functional event sourcing
Denis Mikhaylov
@notxcain
Aecor ⇒ Purely functional event sourcing by @notxcain 1
Vanilla
id owner balance
1 John 50
do debitAccount(accountId: AccountId, amount: Amount) = for {
state <- loadState(accountId)
(nextState, result) <- state.debit(amount)
_ <- saveState(accountId, nextState)
} yield result
Aecor ⇒ Purely functional event sourcing by @notxcain 2
Event sourcing
id seqNr event
Account-1 1 Opened(John)
Account-1 2 Credited(100)
Account-1 3 Debited(50)
Aecor ⇒ Purely functional event sourcing by @notxcain 3
Event sourcing
do debitAccount(accountId: AccountId, amount: Amount) = for {
events <- loadEvents(accountId)
state = events.foldLeft(zeroState)(_ applyEvent _)
(newEvents, result) <- state.debit(amount)
_ <- appendEvents(accountId, newEvents)
} yield result
Aecor ⇒ Purely functional event sourcing by @notxcain 4
Event
• What?
• When?
• Who?
• Where?
Aecor ⇒ Purely functional event sourcing by @notxcain 5
Isn’t that great?
• Change log
• Complex temporal, retroactive queries
• Time-travel debugging
• Scalable, HA stores (Cassandra, Couchbase, etc.)
• Event based service integration (reactive bla bla)
Aecor ⇒ Purely functional event sourcing by @notxcain 6
Toolkit?
Aecor ⇒ Purely functional event sourcing by @notxcain 7
Toolkit?
• Purely functional behaviors
• Purely functional runtime
• Scalable
• Composable
• Testable
Aecor ⇒ Purely functional event sourcing by @notxcain 8
Aecorhttps://aecor.io
Aecor ⇒ Purely functional event sourcing by @notxcain 9
Aecor
• modular framework library
• composable primitives
• runtimes
Aecor ⇒ Purely functional event sourcing by @notxcain 10
Akka Persistence Runtime
• akka-cluster-sharding
• akka-persistence
• akka-persistence-cassandra
• cats
• typesafe encoding/decoding
Aecor ⇒ Purely functional event sourcing by @notxcain 11
Behavior
final case class EventsourcedBehavior[F[_], Op[_], State, Event](
handler: Op ~> Handler[F, State, Event, ?],
zero: State,
reducer: (State, Event) => Folded[State]
)
Aecor ⇒ Purely functional event sourcing by @notxcain 12
State
zero: S
reducer: (S, E) => Folded[E]
sealed abstract class Folded[+A] extends Product with Serializable
final case object Impossible extends Folded[Nothing]
final case class Next[+A](a: A) extends Folded[A]
Aecor ⇒ Purely functional event sourcing by @notxcain 13
Operations
trait RepliesWith[A]
Aecor ⇒ Purely functional event sourcing by @notxcain 14
Operations
trait RepliesWith[A]
sealed abstract class AccountOp[A] extends Product with Serializable {
def accountId: AccountId
}
final case class OpenAccount(accountId: AccountId)
extends AccountOp[Either[OpenAccountRejection, Unit]]
final case class CreditAccount(accountId: AccountId, transactionId: AccountTransactionId, amount: Amount)
extends AccountOp[Either[CreditAccountRejection, Unit]]
final case class DebitAccount(accountId: AccountId, transactionId: AccountTransactionId, amount: Amount)
extends AccountOp[Either[DebitAccountRejection, Unit]]
Aecor ⇒ Purely functional event sourcing by @notxcain 15
Operation handler
type Handler[F[_], S, E, A] =
S => F[(Seq[E], A)]
Aecor ⇒ Purely functional event sourcing by @notxcain 16
Operation handler
type Handler[F[_], S, E, A] =
S => F[(Seq[E], A)]
type VanillaHandler[F[_], S, A] =
S => F[(S, A)]
Aecor ⇒ Purely functional event sourcing by @notxcain 17
Operation handler
type Handler[F[_], S, E, A] =
S => F[(Seq[E], A)]
type OperationHandler[F[_], Op[_], S, E] =
Op ~> Handler[F, S, E, ?]
Aecor ⇒ Purely functional event sourcing by @notxcain 18
Behavior
final case class EventsourcedBehavior[F[_], Op[_], State, Event](
handler: Op ~> Handler[F, State, Event, ?],
zero: State,
reducer: (State, Event) => Folded[State]
)
Aecor ⇒ Purely functional event sourcing by @notxcain 19
Show Time
class AkkaPersistenceRuntime[F[_]: Async: CaptureFuture: Capture: Monad](system: ActorSystem) {
def start[Op[_], State, Event: PersistentEncoder: PersistentDecoder](
entityName: String,
correlation: Correlation[Op], // Op[_] => String
behavior: EventsourcedBehavior[F, Op, State, Event],
tagging: Tagging[Event], // Event => EventTag[Event]
snapshotPolicy: SnapshotPolicy[State],
settings: AkkaPersistenceRuntimeSettings
): F[Op ~> F] = ...
}
Aecor ⇒ Purely functional event sourcing by @notxcain 20
Event tagging
AccountOpened(1, John) -> Account0
AccountOpened(2, Mike) -> Account1
AccountOpened(3, Bill) -> Account0
Aecor ⇒ Purely functional event sourcing by @notxcain 21
Reading events
trait EventJournalQuery[Offset, E] {
def eventsByTag(
tag: EventTag[E],
offset: Option[Offset]
): Source[JournalEntry[Offset, E], NotUsed]
}
Aecor ⇒ Purely functional event sourcing by @notxcain 22
Reading events
trait EventJournalQuery[Offset, E] {
def committableEventsByTag[F[_]: Async](
offsetStore: OffsetStore[F, Offset],
tag: EventTag[E],
consumerId: ConsumerId
): Source[Committable[F, JournalEntry[Offset, E]], NotUsed]
}
final case class Committable[F[_], +A](commit: F[Unit], value: A)
Aecor ⇒ Purely functional event sourcing by @notxcain 23
Process
1. React to changes (events, time)
2. Perform actions
3. ???
4. PROFIT Eventual Consistency
Aecor ⇒ Purely functional event sourcing by @notxcain 24
Co-things?
Entity : Actions -> Changes
Process : Changes -> Actions
Aecor ⇒ Purely functional event sourcing by @notxcain 25
Process
Top 2
• Views
• Business-processes
Aecor ⇒ Purely functional event sourcing by @notxcain 26
Distributed Processing
final case class Process[F[_]](run: F[RunningProcess[F]])
Aecor ⇒ Purely functional event sourcing by @notxcain 27
Distributed Processing
final case class Process[F[_]](run: F[RunningProcess[F]])
final case class RunningProcess[F[_]](
watchTermination: F[Unit],
shutdown: () => Unit
)
Aecor ⇒ Purely functional event sourcing by @notxcain 28
Distributed Processing
def start[F[_]: Async: Capture: CaptureFuture: Functor](
name: String,
processes: Seq[Process[F]],
settings: DistributedProcessingSettings
): F[ProcessKillSwitch[F]] = ...
final case class ProcessKillSwitch[F[_]](shutdown: F[Unit])
Aecor ⇒ Purely functional event sourcing by @notxcain 29
Aecor - what else?
Experimental runtime for generic behaviors
final case class Behavior[F[_], Op[_]](run: Op ~> PairT[F, Behavior[F, Op], ?])
class GenericAkkaRuntime[F[_]: Async: CaptureFuture: Functor: Capture](system: ActorSystem) {
def start[Op[_]](entityName: String,
correlation: Correlation[Op],
behavior: Behavior[F, Op],
settings: GenericAkkaRuntimeSettings
): F[Op ~> F]
}
Aecor ⇒ Purely functional event sourcing by @notxcain 30
Aecor - what else?
Liberator (free algebra for trait, and many more)
@algebra
trait Account[F[_]] {
def openAccount(accountId: AccountId, owner: AccountOwner): F[Either[OpenAccountRejection, Unit]]
}
class EventSourcedAccount[F[_]](clock: F[ZonedDateTime])
extends Account[Handler[F, AccountState, AccountEvent, ?]]
val account: EventSourcedAggregate[Task] = ...
val handler: AccountOp ~> Handler[F, AccountState, AccountEvent, ?] = account.asFunctionK
Aecor ⇒ Purely functional event sourcing by @notxcain 31
Adopters
• Evotor (https://evotor.ru)
• Your name here
Aecor ⇒ Purely functional event sourcing by @notxcain 32
Q&Ahttps://aecor.io
Aecor ⇒ Purely functional event sourcing by @notxcain 33

Aecor. Purely functional event sourcing

  • 1.
    Aecor Purely functional eventsourcing Denis Mikhaylov @notxcain Aecor ⇒ Purely functional event sourcing by @notxcain 1
  • 2.
    Vanilla id owner balance 1John 50 do debitAccount(accountId: AccountId, amount: Amount) = for { state <- loadState(accountId) (nextState, result) <- state.debit(amount) _ <- saveState(accountId, nextState) } yield result Aecor ⇒ Purely functional event sourcing by @notxcain 2
  • 3.
    Event sourcing id seqNrevent Account-1 1 Opened(John) Account-1 2 Credited(100) Account-1 3 Debited(50) Aecor ⇒ Purely functional event sourcing by @notxcain 3
  • 4.
    Event sourcing do debitAccount(accountId:AccountId, amount: Amount) = for { events <- loadEvents(accountId) state = events.foldLeft(zeroState)(_ applyEvent _) (newEvents, result) <- state.debit(amount) _ <- appendEvents(accountId, newEvents) } yield result Aecor ⇒ Purely functional event sourcing by @notxcain 4
  • 5.
    Event • What? • When? •Who? • Where? Aecor ⇒ Purely functional event sourcing by @notxcain 5
  • 6.
    Isn’t that great? •Change log • Complex temporal, retroactive queries • Time-travel debugging • Scalable, HA stores (Cassandra, Couchbase, etc.) • Event based service integration (reactive bla bla) Aecor ⇒ Purely functional event sourcing by @notxcain 6
  • 7.
    Toolkit? Aecor ⇒ Purelyfunctional event sourcing by @notxcain 7
  • 8.
    Toolkit? • Purely functionalbehaviors • Purely functional runtime • Scalable • Composable • Testable Aecor ⇒ Purely functional event sourcing by @notxcain 8
  • 9.
    Aecorhttps://aecor.io Aecor ⇒ Purelyfunctional event sourcing by @notxcain 9
  • 10.
    Aecor • modular frameworklibrary • composable primitives • runtimes Aecor ⇒ Purely functional event sourcing by @notxcain 10
  • 11.
    Akka Persistence Runtime •akka-cluster-sharding • akka-persistence • akka-persistence-cassandra • cats • typesafe encoding/decoding Aecor ⇒ Purely functional event sourcing by @notxcain 11
  • 12.
    Behavior final case classEventsourcedBehavior[F[_], Op[_], State, Event]( handler: Op ~> Handler[F, State, Event, ?], zero: State, reducer: (State, Event) => Folded[State] ) Aecor ⇒ Purely functional event sourcing by @notxcain 12
  • 13.
    State zero: S reducer: (S,E) => Folded[E] sealed abstract class Folded[+A] extends Product with Serializable final case object Impossible extends Folded[Nothing] final case class Next[+A](a: A) extends Folded[A] Aecor ⇒ Purely functional event sourcing by @notxcain 13
  • 14.
    Operations trait RepliesWith[A] Aecor ⇒Purely functional event sourcing by @notxcain 14
  • 15.
    Operations trait RepliesWith[A] sealed abstractclass AccountOp[A] extends Product with Serializable { def accountId: AccountId } final case class OpenAccount(accountId: AccountId) extends AccountOp[Either[OpenAccountRejection, Unit]] final case class CreditAccount(accountId: AccountId, transactionId: AccountTransactionId, amount: Amount) extends AccountOp[Either[CreditAccountRejection, Unit]] final case class DebitAccount(accountId: AccountId, transactionId: AccountTransactionId, amount: Amount) extends AccountOp[Either[DebitAccountRejection, Unit]] Aecor ⇒ Purely functional event sourcing by @notxcain 15
  • 16.
    Operation handler type Handler[F[_],S, E, A] = S => F[(Seq[E], A)] Aecor ⇒ Purely functional event sourcing by @notxcain 16
  • 17.
    Operation handler type Handler[F[_],S, E, A] = S => F[(Seq[E], A)] type VanillaHandler[F[_], S, A] = S => F[(S, A)] Aecor ⇒ Purely functional event sourcing by @notxcain 17
  • 18.
    Operation handler type Handler[F[_],S, E, A] = S => F[(Seq[E], A)] type OperationHandler[F[_], Op[_], S, E] = Op ~> Handler[F, S, E, ?] Aecor ⇒ Purely functional event sourcing by @notxcain 18
  • 19.
    Behavior final case classEventsourcedBehavior[F[_], Op[_], State, Event]( handler: Op ~> Handler[F, State, Event, ?], zero: State, reducer: (State, Event) => Folded[State] ) Aecor ⇒ Purely functional event sourcing by @notxcain 19
  • 20.
    Show Time class AkkaPersistenceRuntime[F[_]:Async: CaptureFuture: Capture: Monad](system: ActorSystem) { def start[Op[_], State, Event: PersistentEncoder: PersistentDecoder]( entityName: String, correlation: Correlation[Op], // Op[_] => String behavior: EventsourcedBehavior[F, Op, State, Event], tagging: Tagging[Event], // Event => EventTag[Event] snapshotPolicy: SnapshotPolicy[State], settings: AkkaPersistenceRuntimeSettings ): F[Op ~> F] = ... } Aecor ⇒ Purely functional event sourcing by @notxcain 20
  • 21.
    Event tagging AccountOpened(1, John)-> Account0 AccountOpened(2, Mike) -> Account1 AccountOpened(3, Bill) -> Account0 Aecor ⇒ Purely functional event sourcing by @notxcain 21
  • 22.
    Reading events trait EventJournalQuery[Offset,E] { def eventsByTag( tag: EventTag[E], offset: Option[Offset] ): Source[JournalEntry[Offset, E], NotUsed] } Aecor ⇒ Purely functional event sourcing by @notxcain 22
  • 23.
    Reading events trait EventJournalQuery[Offset,E] { def committableEventsByTag[F[_]: Async]( offsetStore: OffsetStore[F, Offset], tag: EventTag[E], consumerId: ConsumerId ): Source[Committable[F, JournalEntry[Offset, E]], NotUsed] } final case class Committable[F[_], +A](commit: F[Unit], value: A) Aecor ⇒ Purely functional event sourcing by @notxcain 23
  • 24.
    Process 1. React tochanges (events, time) 2. Perform actions 3. ??? 4. PROFIT Eventual Consistency Aecor ⇒ Purely functional event sourcing by @notxcain 24
  • 25.
    Co-things? Entity : Actions-> Changes Process : Changes -> Actions Aecor ⇒ Purely functional event sourcing by @notxcain 25
  • 26.
    Process Top 2 • Views •Business-processes Aecor ⇒ Purely functional event sourcing by @notxcain 26
  • 27.
    Distributed Processing final caseclass Process[F[_]](run: F[RunningProcess[F]]) Aecor ⇒ Purely functional event sourcing by @notxcain 27
  • 28.
    Distributed Processing final caseclass Process[F[_]](run: F[RunningProcess[F]]) final case class RunningProcess[F[_]]( watchTermination: F[Unit], shutdown: () => Unit ) Aecor ⇒ Purely functional event sourcing by @notxcain 28
  • 29.
    Distributed Processing def start[F[_]:Async: Capture: CaptureFuture: Functor]( name: String, processes: Seq[Process[F]], settings: DistributedProcessingSettings ): F[ProcessKillSwitch[F]] = ... final case class ProcessKillSwitch[F[_]](shutdown: F[Unit]) Aecor ⇒ Purely functional event sourcing by @notxcain 29
  • 30.
    Aecor - whatelse? Experimental runtime for generic behaviors final case class Behavior[F[_], Op[_]](run: Op ~> PairT[F, Behavior[F, Op], ?]) class GenericAkkaRuntime[F[_]: Async: CaptureFuture: Functor: Capture](system: ActorSystem) { def start[Op[_]](entityName: String, correlation: Correlation[Op], behavior: Behavior[F, Op], settings: GenericAkkaRuntimeSettings ): F[Op ~> F] } Aecor ⇒ Purely functional event sourcing by @notxcain 30
  • 31.
    Aecor - whatelse? Liberator (free algebra for trait, and many more) @algebra trait Account[F[_]] { def openAccount(accountId: AccountId, owner: AccountOwner): F[Either[OpenAccountRejection, Unit]] } class EventSourcedAccount[F[_]](clock: F[ZonedDateTime]) extends Account[Handler[F, AccountState, AccountEvent, ?]] val account: EventSourcedAggregate[Task] = ... val handler: AccountOp ~> Handler[F, AccountState, AccountEvent, ?] = account.asFunctionK Aecor ⇒ Purely functional event sourcing by @notxcain 31
  • 32.
    Adopters • Evotor (https://evotor.ru) •Your name here Aecor ⇒ Purely functional event sourcing by @notxcain 32
  • 33.
    Q&Ahttps://aecor.io Aecor ⇒ Purelyfunctional event sourcing by @notxcain 33