Developing functional domain models with event sourcing Chris - - PowerPoint PPT Presentation

developing functional domain models with event sourcing
SMART_READER_LITE
LIVE PREVIEW

Developing functional domain models with event sourcing Chris - - PowerPoint PPT Presentation

Developing functional domain models with event sourcing Chris Richardson Author of POJOs in Action Founder of the original CloudFoundry.com @crichardson chris@chrisrichardson.net http://plainoldobjects.com http://microservices.io


slide-1
SLIDE 1

@crichardson

Developing functional domain models with event sourcing

Chris Richardson

Author of POJOs in Action Founder of the original CloudFoundry.com @crichardson chris@chrisrichardson.net http://plainoldobjects.com http://microservices.io

slide-2
SLIDE 2

@crichardson

Presentation goal

How to develop functional domain models based on event sourcing

slide-3
SLIDE 3

@crichardson

About Chris

Consultant & Founder of Eventuate.IO

slide-4
SLIDE 4

@crichardson

For more information

http://microservices.io http://github.com/cer/microservices-examples/ https://github.com/cer/event-sourcing-examples http://plainoldobjects.com/ https://twitter.com/crichardson http://eventuate.io/

slide-5
SLIDE 5

@crichardson

Agenda

Why event sourcing? Designing a domain model based on event sourcing Event sourcing and service design Microservices and event sourcing

slide-6
SLIDE 6

@crichardson

Tomcat

Traditional monolithic architecture

Browser/ Client

WAR/EAR

RDBMS

Customers Accounts

Transfers

Banking UI

develop test deploy

Simple

Load balancer

scale

Spring MVC Spring Hibernate

...

HTML REST/JSON

ACID

slide-7
SLIDE 7

@crichardson

But large and/or complex monolithic applications = Trouble!

slide-8
SLIDE 8

@crichardson

Using a single RDBMS has its limitations

slide-9
SLIDE 9

@crichardson

Apply the scale cube

X axis

  • horizontal duplication

Z axis - data partitioning Y axis - functional decomposition Scale by splitting similar things Scale by splitting different things

slide-10
SLIDE 10

@crichardson

Today: use a microservice, polyglot architecture

Banking UI Account Management Service MoneyTransfer Management Service Account Database MoneyTransfer Database

Standalone services

Sharded SQL NoSQL DB

slide-11
SLIDE 11

@crichardson

But now we have distributed data management problems

slide-12
SLIDE 12

@crichardson

Example: Money transfer

Account Management Service MoneyTransfer Management Service Account Database MoneyTransfer Database Account #1 Money Transfer Account #2 No 2PC No ACID NoSQL SQL

slide-13
SLIDE 13

@crichardson

Use an event-driven architecture

Services publish events when state changes Services subscribe to events and update their state Maintain eventual consistency across multiple aggregates (in multiple datastores) Synchronize replicated data

slide-14
SLIDE 14

@crichardson

MoneyTransferService

MoneyTransfer fromAccountId = 101 toAccountId = 202 amount = 55 state = INITIAL MoneyTransfer fromAccountId = 101 toAccountId = 202 amount = 55 state = DEBITED MoneyTransfer fromAccountId = 101 toAccountId = 202 amount = 55 state = COMPLETED

Eventually consistent money transfer

Message Bus AccountService

transferMoney()

Publishes: Subscribes to: Subscribes to: publishes:

MoneyTransferCreatedEvent AccountDebitedEvent DebitRecordedEvent AccountCreditedEvent MoneyTransferCreatedEvent DebitRecordedEvent AccountDebitedEvent AccountCreditedEvent Account id = 101 balance = 250 Account id = 202 balance = 125 Account id = 101 balance = 195 Account id = 202 balance = 180

slide-15
SLIDE 15

@crichardson

How to atomically update the database and publish an event without 2PC? (dual write problem)

slide-16
SLIDE 16

@crichardson

Event sourcing

For each aggregate: Identify (state-changing) domain events Define Event classes For example, Account: AccountOpenedEvent, AccountDebitedEvent, AccountCreditedEvent ShoppingCart: ItemAddedEvent, ItemRemovedEvent, OrderPlacedEvent

slide-17
SLIDE 17

@crichardson

Persists events NOT current state

Account balance

  • pen(initial)

debit(amount) credit(amount) AccountOpened

Event table

AccountCredited AccountDebited 101 450

Account table

X

101 101 101 901 902 903 500 250 300

slide-18
SLIDE 18

@crichardson

Replay events to recreate state

Account balance

AccountOpenedEvent(balance) AccountDebitedEvent(amount) AccountCreditedEvent(amount)

Events

slide-19
SLIDE 19

@crichardson

Before: update state + publish events

Two actions that must be atomic Single action that can be done atomically

Now: persist (and publish) events

slide-20
SLIDE 20

@crichardson

Optimizing using snapshots

Most aggregates have relatively few events BUT consider a 10-year old Account ⇒ many transactions Therefore, use snapshots: Periodically save snapshot of aggregate state Typically serialize a memento of the aggregate Load latest snapshot + subsequent events

slide-21
SLIDE 21

@crichardson

Request handling in an event-sourced application

HTTP Handler Event Store

pastEvents = findEvents(entityId)

Account

new() applyEvents(pastEvents) newEvents = processCmd(SomeCmd) saveEvents(newEvents)

Microservice A

(optimistic locking)

slide-22
SLIDE 22

@crichardson

Event Store publishes events - consumed by other services

Event Store Event Subscriber

subscribe(EventTypes) publish(event) publish(event)

Aggregate NoSQL materialized view

update() update()

Microservice B

slide-23
SLIDE 23

@crichardson

About the event store

Hybrid database and message broker Retrieve events for an aggregate Append to an aggregates events Subscribe to events Event store implementations: Home-grown/DIY geteventstore.com by Greg Young My event store - bit.ly/trialeventuate

slide-24
SLIDE 24

@crichardson

Business benefits of event sourcing

Built-in, reliable audit log Enables temporal queries Publishes events needed by big data/predictive analytics etc. Preserved history ⇒ More easily implement future requirements

slide-25
SLIDE 25

@crichardson

Technical benefits of event sourcing

Solves data consistency issues in a Microservice/NoSQL-based architecture: Atomically save and publish events Event subscribers update other aggregates ensuring eventual consistency Event subscribers update materialized views in SQL and NoSQL databases (more on that later) Eliminates O/R mapping problem

slide-26
SLIDE 26

@crichardson

Drawbacks of event sourcing

Weird and unfamiliar Events = a historical record of your bad design decisions Handling duplicate events can be tricky

Application must handle eventually consistent data

Event store only directly supports PK-based lookup (more on that later)

slide-27
SLIDE 27

@crichardson

Agenda

Why event sourcing? Designing a domain model based on event sourcing Event sourcing and service design Microservices and event sourcing

slide-28
SLIDE 28

@crichardson

Use the familiar building blocks

  • f DDD

Entity Value object Services Repositories Aggregates

With some differences

slide-29
SLIDE 29

@crichardson

Partition the domain model into Aggregates

slide-30
SLIDE 30

Aggregate design

Graph consisting of a root entity and one or more other entities and value objects Each core business entity = Aggregate: e.g. customer, Account, Order, Product, …. Reference other aggregate roots via primary key Often contains partial copy

  • f other aggregates’ data

Order OrderLine Item quantity productId productName productPrice customerId Address street city …

slide-31
SLIDE 31

@crichardson

Aggregate granularity is important

Transaction = processing one command by one aggregate No opportunity to update multiple aggregates within a transaction If an update must be atomic (i.e. no compensating transaction) then it must be handled by a single aggregate e.g. scanning boarding pass at security checkpoint or when entering jetway

slide-32
SLIDE 32

@crichardson

Aggregate granularity

Forum Post User

moderator author

Forum Post User

moderator author

Forum Post User

moderator author

Consistency Scalability/ User experience

slide-33
SLIDE 33

@crichardson

ES-based Aggregate design

class Account { var balance : Money; def debit(amount : Money) { balance = balance - amount } } case class Account(balance : Money) { def processCommand(cmd : Command) : Seq[Event] = ??? def applyEvent(event : Event) : Account = … } case class DebitCommand(amount : Money) case class AccountDebitedEvent(amount : Money)

Classic, mutable domain model Event centric, immutable

slide-34
SLIDE 34

Designing domain events

Record state changes for an aggregate Part of the public API of the domain model

ProductAddedToCart id : TimeUUID productId productName productPrice shoppingCartId Required by aggregate Enrichment: Used by consumers

slide-35
SLIDE 35

@crichardson

Designing commands

Created by a service from incoming request Processed by an aggregate Immutable Contains value objects for Validating request Creating event Auditing user activity

slide-36
SLIDE 36

@crichardson

Events and Commands

slide-37
SLIDE 37

@crichardson

Hybrid OO/FP domain objects

slide-38
SLIDE 38

@crichardson

OO = State + Behavior

balance Account processCommand(cmd : Command) : Seq[Events] applyEvent(event : Event) : Account State Behavior

slide-39
SLIDE 39

@crichardson

Aggregate traits

Map Command to Events Apply event returning updated Aggregate

Used by Event Store to reconstitute aggregate

slide-40
SLIDE 40

@crichardson

Account - command processing

Prevent

  • verdraft
slide-41
SLIDE 41

@crichardson

Account - applying events

Immutable

slide-42
SLIDE 42

@crichardson

Event Store API

trait EventStore { def save[T <: Aggregate[T]](entity: T, events: Seq[Event], assignedId : Option[EntityId] = None): Future[EntityWithIdAndVersion[T]] def update[T <: Aggregate[T]](entityIdAndVersion : EntityIdAndVersion, entity: T, events: Seq[Event]): Future[EntityWithIdAndVersion[T]] def find[T <: Aggregate[T] : ClassTag](entityId: EntityId) : Future[EntityWithIdAndVersion[T]] def findOptional[T <: Aggregate[T] : ClassTag](entityId: EntityId) Future[Option[EntityWithIdAndVersion[T]]] def subscribe(subscriptionId: SubscriptionId): Future[AcknowledgableEventStream] }

slide-43
SLIDE 43

@crichardson

FP-style domain objects

slide-44
SLIDE 44

@crichardson

FP = Separation of State and Behavior

Account balance AccountAggregate processCommand(Account, Command) : Seq[Events] applyEvent(Account, Event) : Account State Behavior

slide-45
SLIDE 45

@crichardson

Aggregate type classes/implicits

Used by Event Store to reconstitute aggregate

slide-46
SLIDE 46

@crichardson

Functional-style Account Aggregate

State Behavior

slide-47
SLIDE 47

@crichardson

Functional-style Account Aggregate

Behavior

slide-48
SLIDE 48

@crichardson

FP-style event store

Enables inference of T, and EV Tells ES how to instantiate aggregate and apply events = Strategy

slide-49
SLIDE 49

@crichardson

Haskell aggregate

https://gist.github.com/Fristi/7327904

slide-50
SLIDE 50

@crichardson

Haskell TicTacToe aggregate

slide-51
SLIDE 51

@crichardson

JavaScript aggregate

slide-52
SLIDE 52

@crichardson

Agenda

Why event sourcing? Designing a domain model based on event sourcing Event sourcing and service design Microservices and event sourcing

slide-53
SLIDE 53

@crichardson

Designing services

Responsibilities and collaborations Invoked by adapter, e.g. HTTP controller Creates a Command Selects new aggregate or existing aggregate to process command Load aggregate from same bounded context, e.g. Add a Post to a Forum - load forum Load data from another other bounded context, e.g. addProductToCart() Requests ProductInfo from ProductService Invokes PricingService to calculate discount price Sometimes loads target aggregate before creating command e.g. addProductToCart() needs contents of shopping cart to calculate discounted price of product to add

slide-54
SLIDE 54

@crichardson

Money transfer example

As a customer of the bank I want to transfer money between two bank accounts So that I don't have to write a check Given that my open savings account balance is $150 Given that my open checking account balance is $10 When I transfer $50 from my savings account to my checking account Then my savings account balance is $100 Then my checking account balance is $60 Then a MoneyTransfer was created Story Scenario Post conditions Pre conditions

slide-55
SLIDE 55

@crichardson

Old-style ACID…

public class MoneyTransferServiceImpl …{ private final AccountRepository accountRepository; private final MoneyTransferRepository moneyTransferRepository; … @Transactional public MoneyTransfer transfer( String fromAccountId, String toAccountId, double amount) throws MoneyTransferException { Account fromAccount = accountRepository.findAccount(fromAccountId); Account toAccount = accountRepository.findAccount(toAccountId); // … Verify accounts are open … fromAccount.debit(amount); toAccount.credit(amount); return moneyTransferRepository.createMoneyTransfer( fromAccount, toAccount, amount); } }

slide-56
SLIDE 56

… becomes eventually consistent (BASE)

Updating multiple aggregates multi-step, event-driven flow each step updates one Aggregate Service creates saga to coordinate workflow A state machine Part of the domain, e.g. MoneyTransfer aggregate OR Synthetic aggregate Post-conditions eventually true

Money
 Transfer From Account To Account

created debited debit recorded credited

public MoneyTransfer transfer() { … Creates MoneyTransfer … }

slide-57
SLIDE 57

@crichardson

Need compensating transactions

Pre-conditions might be false when attempting to update an aggregate For example: an account might be closed transferring money: from account when debiting ⇒ stop transfer to account ⇒ reverse the debit from account when attempting reversal ⇒ bank wins!

slide-58
SLIDE 58

@crichardson

MoneyTransferService

Remoting proxy DSL concisely specifies: 1.Creates Account aggregate 2.Processes command 3.Applies events 4.Persists events

slide-59
SLIDE 59

@crichardson

Event handling in Account

1.Load Account aggregate 2.Processes command 3.Applies events 4.Persists events Durable subscription name Triggers BeanPostProcessor

slide-60
SLIDE 60

@crichardson

JavaScript service

slide-61
SLIDE 61

@crichardson

Agenda

Why event sourcing? Designing a domain model based on event sourcing Event sourcing and service design Microservices and event sourcing

slide-62
SLIDE 62

@crichardson

Event Store only supports PK- based lookup Therefore….

slide-63
SLIDE 63

@crichardson

ES+CQRS-based microservices architecture

Event Store Command-side

Updates Events

Query-side

Queries

(Denormalized) View

Events

Aggregate

slide-64
SLIDE 64

@crichardson

Modular domain model

Forum Post User

moderator author

Forum Post User

moderator author

Tightly coupled ACID Loosely coupled aggregates Eventually consistent

slide-65
SLIDE 65

@crichardson

MonolithicFirst approach

Tomcat

WAR/EAR Forum Post User

moderator author

Not entirely free though - Event Sourcing premium

slide-66
SLIDE 66

@crichardson

But no Big Ball of Mud to untangle

slide-67
SLIDE 67

@crichardson

Microservices deployment

Tomcat

WAR/EAR

Much higher - microservices premium

Forum

Tomcat

WAR/EAR Post

Tomcat

WAR/EAR User

slide-68
SLIDE 68

@crichardson

Summary

Event sourcing solves a variety of problems in modern application architectures Scala is a great language for implementing ES-based domain models: Case classes Pattern matching Recreating state = functional fold over events But Java, JavaScript and Haskell work too! ES-based architecture = flexible deployment

slide-69
SLIDE 69

@crichardson

@crichardson chris@chrisrichardson.net http://plainoldobjects.com http://microservices.io