developing functional domain models with event sourcing qconny

69
@crichardson Developing functional domain models with event sourcing Chris Richardson Author of POJOs in Action Founder of the original CloudFoundry.com @crichardson [email protected] http://plainoldobjects.com http://microservices.io

Upload: trinhcong

Post on 14-Feb-2017

229 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Developing functional domain models with event sourcing QCONNY

@crichardson

Developing functional domain models with event sourcingChris Richardson

Author of POJOs in Action Founder of the original CloudFoundry.com

@crichardson [email protected] http://plainoldobjects.com http://microservices.io

Page 2: Developing functional domain models with event sourcing QCONNY

@crichardson

Presentation goal

How to develop functional domain models based on event

sourcing

Page 3: Developing functional domain models with event sourcing QCONNY

@crichardson

About Chris

Consultant & Founder of Eventuate.IO

Page 4: Developing functional domain models with event sourcing QCONNY

@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/

Page 5: Developing functional domain models with event sourcing QCONNY

@crichardson

Agenda

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

Page 6: Developing functional domain models with event sourcing QCONNY

@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

...

HTMLREST/JSON

ACID

Page 7: Developing functional domain models with event sourcing QCONNY

@crichardson

But large and/or complex monolithic applications

= Trouble!

Page 8: Developing functional domain models with event sourcing QCONNY

@crichardson

Using a single RDBMS has its limitations

Page 9: Developing functional domain models with event sourcing QCONNY

@crichardson

Apply the scale cube

X axis - horizontal duplication

Z axis

- data

partit

ioning

Y axis - functional

decomposition

Scale b

y split

ting s

imilar

thing

s

Scale by splitting

different things

Page 10: Developing functional domain models with event sourcing QCONNY

@crichardson

Today: use a microservice, polyglot architecture

Banking UI

Account Management Service MoneyTransfer Management Service

Account Database MoneyTransfer Database

Standalone services

Sharded SQLNoSQL DB

Page 11: Developing functional domain models with event sourcing QCONNY

@crichardson

But now we have distributed data management

problems

Page 12: Developing functional domain models with event sourcing QCONNY

@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

Page 13: Developing functional domain models with event sourcing QCONNY

@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

Page 14: Developing functional domain models with event sourcing QCONNY

@crichardson

MoneyTransferServiceMoneyTransfer

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

AccountCreditedEventMoneyTransferCreatedEvent

DebitRecordedEvent

AccountDebitedEventAccountCreditedEvent

Account id = 101 balance = 250

Account id = 202 balance = 125

Account id = 101 balance = 195

Account id = 202 balance = 180

Page 15: Developing functional domain models with event sourcing QCONNY

@crichardson

How to atomically

update the database and

publish an event without 2PC?

(dual write problem)

Page 16: Developing functional domain models with event sourcing QCONNY

@crichardson

Event sourcing

For each aggregate: Identify (state-changing) domain events Define Event classes

For example, Account: AccountOpenedEvent, AccountDebitedEvent, AccountCreditedEvent ShoppingCart: ItemAddedEvent, ItemRemovedEvent, OrderPlacedEvent

Page 17: Developing functional domain models with event sourcing QCONNY

@crichardson

Persists events NOT current state

Account

balance

open(initial) debit(amount) credit(amount)

AccountOpened

Event table

AccountCredited

AccountDebited

101 450

Account tableX101

101

101

901

902

903

500

250

300

Page 18: Developing functional domain models with event sourcing QCONNY

@crichardson

Replay events to recreate state

Account

balance

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

Events

Page 19: Developing functional domain models with event sourcing QCONNY

@crichardson

Before: update state + publish events

Two actions that must be atomic

Single action that can be done atomically

Now: persist (and publish) events

Page 20: Developing functional domain models with event sourcing QCONNY

@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

Page 21: Developing functional domain models with event sourcing QCONNY

@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)

Page 22: Developing functional domain models with event sourcing QCONNY

@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

Page 23: Developing functional domain models with event sourcing QCONNY

@crichardson

About the event storeHybrid 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

Page 24: Developing functional domain models with event sourcing QCONNY

@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

Page 25: Developing functional domain models with event sourcing QCONNY

@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

Page 26: Developing functional domain models with event sourcing QCONNY

@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)

Page 27: Developing functional domain models with event sourcing QCONNY

@crichardson

Agenda

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

Page 28: Developing functional domain models with event sourcing QCONNY

@crichardson

Use the familiar building blocks of DDD

Entity Value object Services Repositories Aggregates

With some differences

Page 29: Developing functional domain models with event sourcing QCONNY

@crichardson

Partition the domain model

into Aggregates

Page 30: Developing functional domain models with event sourcing QCONNY

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 of other aggregates’ data

Order

OrderLine Item

quantity productId productName productPrice

customerId

Address

street city …

Page 31: Developing functional domain models with event sourcing QCONNY

@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

Page 32: Developing functional domain models with event sourcing QCONNY

@crichardson

Aggregate granularity

Forum

Post

User

moderator

author

Forum

Post

User

moderator

author

Forum

Post

User

moderator

author

Consistency Scalability/ User experience

Page 33: Developing functional domain models with event sourcing QCONNY

@crichardson

ES-based Aggregate designclass 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

Page 34: Developing functional domain models with event sourcing QCONNY

Designing domain eventsRecord 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

Page 35: Developing functional domain models with event sourcing QCONNY

@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

Page 36: Developing functional domain models with event sourcing QCONNY

@crichardson

Events and Commands

Page 37: Developing functional domain models with event sourcing QCONNY

@crichardson

Hybrid OO/FP domain objects

Page 38: Developing functional domain models with event sourcing QCONNY

@crichardson

OO = State + Behavior

balance

Account

processCommand(cmd : Command) : Seq[Events]

applyEvent(event : Event) : Account

State

Behavior

Page 39: Developing functional domain models with event sourcing QCONNY

@crichardson

Aggregate traits

Map Command to Events

Apply event returning updated Aggregate

Used by Event Store to reconstitute aggregate

Page 40: Developing functional domain models with event sourcing QCONNY

@crichardson

Account - command processing

Prevent overdraft

Page 41: Developing functional domain models with event sourcing QCONNY

@crichardson

Account - applying events

Immutable

Page 42: Developing functional domain models with event sourcing QCONNY

@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] }

Page 43: Developing functional domain models with event sourcing QCONNY

@crichardson

FP-style domain objects

Page 44: Developing functional domain models with event sourcing QCONNY

@crichardson

FP = Separation of State and Behavior

Account

balance

AccountAggregate

processCommand(Account, Command) : Seq[Events]

applyEvent(Account, Event) : Account

StateBehavior

Page 45: Developing functional domain models with event sourcing QCONNY

@crichardson

Aggregate type classes/implicits

Used by Event Store to reconstitute aggregate

Page 46: Developing functional domain models with event sourcing QCONNY

@crichardson

Functional-style Account Aggregate

State Behavior

Page 47: Developing functional domain models with event sourcing QCONNY

@crichardson

Functional-style Account Aggregate

Behavior

Page 48: Developing functional domain models with event sourcing QCONNY

@crichardson

FP-style event storeEnables inference of

T, and EV

Tells ES how to instantiate aggregate and apply events

= Strategy

Page 49: Developing functional domain models with event sourcing QCONNY

@crichardson

Haskell aggregate

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

Page 50: Developing functional domain models with event sourcing QCONNY

@crichardson

Haskell TicTacToe aggregate

Page 51: Developing functional domain models with event sourcing QCONNY

@crichardson

JavaScript aggregate

Page 52: Developing functional domain models with event sourcing QCONNY

@crichardson

Agenda

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

Page 53: Developing functional domain models with event sourcing QCONNY

@crichardson

Designing servicesResponsibilities 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

Page 54: Developing functional domain models with event sourcing QCONNY

@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

Page 55: Developing functional domain models with event sourcing QCONNY

@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); } }

Page 56: Developing functional domain models with event sourcing QCONNY

… 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

MoneyTransfer

From Account

To Account

createddebited debit

recorded

credited

public MoneyTransfer transfer() { … Creates MoneyTransfer … }

Page 57: Developing functional domain models with event sourcing QCONNY

@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!

Page 58: Developing functional domain models with event sourcing QCONNY

@crichardson

MoneyTransferService Remoting proxy

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

Page 59: Developing functional domain models with event sourcing QCONNY

@crichardson

Event handling in Account

1.Load Account aggregate 2.Processes command 3.Applies events 4.Persists events

Durable subscription nameTriggers BeanPostProcessor

Page 60: Developing functional domain models with event sourcing QCONNY

@crichardson

JavaScript service

Page 61: Developing functional domain models with event sourcing QCONNY

@crichardson

Agenda

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

Page 62: Developing functional domain models with event sourcing QCONNY

@crichardson

Event Store only supports PK-based lookup Therefore….

Page 63: Developing functional domain models with event sourcing QCONNY

@crichardson

ES+CQRS-based microservices architecture

Event Store

Command-side

Updates

Events

Query-side

Queries

(Denormalized) View

Events

Aggregate

Page 64: Developing functional domain models with event sourcing QCONNY

@crichardson

Modular domain model

Forum

Post

User

moderator

author

Forum

Post

User

moderator

author

Tightly coupled ACID

Loosely coupled aggregates Eventually consistent

Page 65: Developing functional domain models with event sourcing QCONNY

@crichardson

MonolithicFirst approach

Tomcat

WAR/EAR

Forum

Post

User

moderator

author

Not entirely free though - Event Sourcing premium

Page 66: Developing functional domain models with event sourcing QCONNY

@crichardson

But no Big Ball of Mud to untangle

Page 67: Developing functional domain models with event sourcing QCONNY

@crichardson

Microservices deployment

Tomcat

WAR/EAR

Much higher - microservices premium

Forum

Tomcat

WAR/EAR

Post

Tomcat

WAR/EAR

User

Page 68: Developing functional domain models with event sourcing QCONNY

@crichardson

SummaryEvent 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

Page 69: Developing functional domain models with event sourcing QCONNY

@crichardson

@crichardson [email protected]

http://plainoldobjects.com http://microservices.io