Should you be using an
Event Driven Architecture?
Jeppe Cramon - @jeppec
Cloud Create ApS
@jeppec
@jeppec
Check-out microcph.dk
for more details!
@jeppec
“Example isn’t another way to teach,
it is the only way to teach”
Albert Einstein
@jeppec
Let’s look at Online Shopping
@jeppec
Most sites follow a
similar pattern
@jeppec
save(order)
@jeppec
public class OrderService {
public void save(Order order) {
orderRepository.add(order);
if (inventoryService.areOrderItemsInStock(order)) {
order.setStatus(OrderStatus.ItemsInStock);
shippingService.startPackagingOrder(order);
emailService.send(generateOrderOrderItemsInStockEmail(order));
} else {
order.setStatus(OrderStatus.NotAllItemsInStock);
emailService.send(generateOrderItemsNotInStockEmail(order));
executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1));
}
}
public void checkOrderInventoryStatus(OrderId orderId) {
Order order = orderRepository.load(orderId);
if (inventoryService.areOrderItemsInStock(order)) {
order.setStatus(OrderStatus.ItemsInStock);
shippingService.startPackagingOrder(order);
emailService.send(generateOrderOrderItemsInStockEmail(order));
} else {
executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1));
}
}
Side effect. What happens if we call save() twice?
@jeppec
public class OrderService {
public void save(Order order) {
orderRepository.add(order);
if (inventoryService.areOrderItemsInStock(order)) {
order.setStatus(OrderStatus.ItemsInStock);
shippingService.startPackagingOrder(order);
…
@jeppec
Let’s decouple Order- and
Inventory Service further
public class OrderService {
public void save(Order order) {
orderRepository.add(order);
if (inventoryService.areOrderItemsInStock(order)) {
order.setStatus(OrderStatus.ItemsInStock);
shippingService.startPackagingOrder(order);
@jeppec
public class OrderService {
public void save(Order order) {
orderRepository.add(order);
List<ProductId> productIdsOutOfStock = inventoryService.checkProductStockStatus(
order.orderLines.toMap(OrderLine::productId, OrderLine::quantity));
if (productIdsOutOfStock.isEmpty()) {
order.setStatus(OrderStatus.ItemsInStock);
shippingService.startPackagingOrder(order);
emailService.send(generateOrderOrderItemsInStockEmail(order));
} else {
order.setStatus(OrderStatus.NotAllItemsInStock);
emailService.send(generateOrderItemsNotInStockEmail(order, productIdsOutOfStock));
executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1));
}
}
@jeppec
What about
race conditions?
@jeppec
public class OrderService {
public void save(Order order) {
orderRepository.add(order);
List<ProductId> productIdsOutOfStock = inventoryService.checkProductStockStatus(
order.orderLines.toMap(OrderLine::productId, OrderLine::quantity));
if (productIdsOutOfStock.isEmpty()) {
order.setStatus(OrderStatus.ItemsInStock);
shippingService.startPackagingOrder(order);
emailService.send(generateOrderOrderItemsInStockEmail(order));
} else {
order.setStatus(OrderStatus.NotAllItemsInStock);
emailService.send(generateOrderItemsNotInStockEmail(order, productIdsOutOfStock));
executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1));
}
}
@jeppec
public class OrderService {
public void save(Order order) {
orderRepository.add(order);
List<ProductId> productIdsOutOfStock = inventoryService.reserveProductsForOrder(order.orderId,
order.orderLines.toMap(OrderLine::productId, OrderLine::quantity));
if (productIdsOutOfStock.isEmpty()) {
order.setStatus(OrderStatus.ItemsInStock);
shippingService.startPackagingOrder(order);
emailService.send(generateOrderOrderItemsInStockEmail(order));
} else {
order.setStatus(OrderStatus.NotAllItemsInStock);
emailService.send(generateOrderItemsNotInStockEmail(order, productIdsOutOfStock));
executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1));
}
}
Introduces an additional side effect
@jeppec
Essential complexity of 2 way integration
Inventory
Service
Order
Service
Shipping
Service
UI
Start-
Packaging
Save-Order
Order:Save-Order()
call Inventory:Reserve-Products()
call Shipping:Start-Packaging()
commit()
Reserve-
Products
Local transaction between the 3
“Services”
@jeppec
Let’s (micro)service’ify this
@jeppec
Accidental complexity from distributed service integration
Inventory
Service
Order
Service
Shipping
Service
UI
Start-
Packaging
Save-Order
Reserve-
Products
Local transaction between 2 local
“Services”Remote call
@jeppec
Remote Call?
RPC/REST/SOAP/…
@jeppec
What’s the challenge with
using a Pull model between
distributed components?
@jeppec
@jeppec
Synchronous calls lower our tolerance for faults
• When you get an IO error
• When servers crash or restarts
• When databases are down
• When deadlocks occurs in our databases
• Do you retry?
With synchronous style Service interaction we can loose business data if there’s no automatic retry
Or we risk creating data more than once if the operation isn’t idempotent*
Client Server
Duplicated Response
Duplicated Request
Processing
Response
Request Processing
The same message can be
processed more than once
*Idempotence describes the quality of an operation
in which result and state does not change if the
operation is performed more than 1 time
@jeppec
Idempotence
Don’t press the button twice!
@jeppec
Ensuring
consistency
is much harder
@jeppec
Sales system
Sale
Delivery
system Deliveries
Customer/CRM
system Customer
SAP Bookkeeping
Complete
Purchase
Transaction
Coordinator
Transactional
Resource
Prepare
Phase
Commit
Phase
2 Phase Commit
@jeppec
What’s wrong with distributed transactions?
• Transactions lock resources while active
• Services are autonomous
• Can’t be expected to finish within a certain time interval
• Locking keeps other transactions from completing their job
• Locking doesn’t scale
• X Phase Commit is fragile by design
@jeppec
Using the pull model
we’re prone to
cascading failure
@jeppec
Order:Save-Order()
call Inventory:Reserve-Products()
call Shipping:Start-Packaging()
if (Shipping:Call-Failed:Too-Busy?)
Wait-A-While()
call Shipping:Start-Packaging()
if (Shipping:Call-Failed:Too-Busy?)
Wait-A-Little-While-Longer()
call Shipping:Start-Packaging()
if (Shipping:Call-Failed:IO-Error?)
Save-We-Need-Check-If-Call-Shipping-Succeded-After-All
AND We-Need-To-Retry call Order:Save-Order and call Inventory:Reserve-Products
AND Tell-Customer-That-This-Operation-Perhaps-Went-Well
if (Shipping:Call-Went-Well?)
commit()
Accidental complexity from distributed service integration
Inventory
Service
Order
Service
Shipping
Service
UI
Start-
Packaging
Save-Order
Reserve-
Products
Local transaction between 2
Components
@jeppec
“A distributed system is one
where a machine I’ve never heard
of can cause my program to fail”
— Leslie Lamport
@jeppec
Service autonomy
Order Inventory
Shipping
Retail System
Shipping
Order Inventory
Retail System
Slow/unreliable network
Different SLA
Slow system
@jeppec
Availability goes down
(without additional instances of each service)
Combined availability: 97%
@jeppec
Services != Decoupling
@jeppec
But surely RPC is the only way
to do this?
@jeppec
We need to switch from a pull
to a push model
Which means we need to change our approach
slightly but profoundly
@jeppec
Most applications are built with CRUD as the main
paradigm
Intention gets lost
@jeppec
We need to shift focus from pure data
towards intent and process automation
This means a change from CRUD style application design, where the
process was implicit and stored in the minds of the users
First I need to
enter the
employee in
this screen
Then I need to press
a button in the user
system so they’re
properly created
And the I need to
add their access
card to this screen
and press Activate
@jeppec
We need to capture User Intent at the UI
CRUD style
Task based style
Intent
@jeppec
Task-based/Inductive UI
• Traditional CRUD UI is what I would call a WHAT UI
• Task based UI’s focuses on HOW the user wants to use the
application
• Guides users through the work process
• The UI becomes an intrinsic part of the design
• The UI design directly affects our commands and thereby our
transactional boundaries
@jeppec
Capturing intent in the form of a Command
A Command is prescriptive of what should happen, and its primary goal is to capture USER
INTENT
A Command
supports a single usecase and
targets a single business object
within a single Transaction
Commands always carry a name in its imperative form:
• AcceptOrder
• ShipOrder
• CancelOrder
• ReimburseCustomer
• Etc.
“A command describes a Task that you want someone else to carry out
for you and where the recipient can choose to reject the Command”
@jeppec
Let’s capture the intent of the customer
public class OrderService {
public void handle(AcceptOrder cmd) {
orderRepository.add(new Order(cmd.orderId,
cmd.orderLines));
????
}
@jeppec
How do we get the
ball moving now that we can’t
use RPC?
@jeppec
Life Beyond Distributed Transactions
by Pat Helland
1. How do we split our data
2. How do we identify our data
3. How do we communicate between our services
@jeppec
1. How do we split our data
Data must be collected in pieces called aggregates.
These aggregates should be limited in size (but not smaller), so that,
after a transaction they are consistent.
Rule of thumb:
One transaction involves only one aggregate.
@jeppec
Domain Driven Design
The term Aggregate comes from DDD
@jeppec
Aggregates
Invoice
InvoiceLine
*
Account *
What:
• Cluster coherent Entities and Value Objects, with
complex associations into Aggregates with well
defined boundaries.
• Choose one entity to be root and control access to
objects inside the boundary through the root.
Motivation:
Control invariants and consistency through the aggregate root.
Ensuring consistency & transactional boundaries for Distributed scenarios!
Root
*
*
@jeppec
2. How do we identify our data
According to Pat Helland we need to be able to uniquely identify each
Aggregate using an ID.
• This ID will usually a UUID/GUID
{21EC2020-3AEA-4069-A2DD-08002B30309D}
2122 (approximately 5.3×1036) combinations
@jeppec
Aggregates refer to each other by ID
they NEVER use memory pointers, join tables or remote calls
@jeppec
Services/Bounded Contexts and Aggregates
Order
Product
Customer
customerId
…
Order
orderId
customerId
…
OrderLine
orderId
productId
quantity
price
ProductCategory
productCategoryId
…
Product
productId
productCategoryId
name
tag
...
@jeppec
3. How do we communicate between our services
What do we do when our process involves more than
one aggregate and therefore likely more than one
service?
@jeppec
Synchronous calls are the crystal meth of programming
At first you make good progress but then the sheer horror
becomes evident when you realise the scalability limitations
and how the brittleness holds back both performance and
development flexibility. By then it is too late to save.
http://www.infoq.com/news/2014/10/thompson-reactive-manifesto-2
We need the reactive properties and then apply protocols
for the message interactions. Without considering the
protocols of interaction this world of micro-services will
become a coordination nightmare.
Martin Thompson
@jeppec
Reactive?
@jeppec
Coupling matrix*
* Modified version of Ian Robinson’s matrix: http://iansrobinson.com/2009/04/27/temporal-and-behavioural-coupling/
Behavioral
coupling
Temporal
coupling
Low High
Low
High
Reactive/
Event oriented
Command oriented
Emergency services Distributed 3 layer
@jeppec
We need to change focus from short
technical transactions
To long running business transactions supporting
business processes
@jeppec
Using Business Events to drive Business Processes
Order Service
Shipping
Billing
Sales
Customers
MessageChannel
Online Ordering System
Web Shop
(Composite UI)
Inventory Service
Shipping Service
Order
Accepted
AcceptOrder
The sales
fulfillment
processing can
now begin…
@jeppec
Events
An Event is non-prescriptive of what should happen in other parts of the system.
An event will typically be published to multiple consumers/subscribers:
• The publisher of the event does not know who the subscribers are
• And the publisher doesn’t know what the subscribers intend to do with the event
Events always carry a name in its past-tense form:
OrderWasAccepted
OrderHasShipped
CustomerWasReimbursed
“An Event describes something that HAS happened”
@jeppec
Push model
• Event based integration follows a push based model that breaks
temporal coupling and avoids cascading failures
• Events can be exchanged between services over a message channel
(that can be implemented using a push or pull protocol)
Sender
Address Changed Event
Receiver
Address Changed Event
Channel
Asynchronous Communication – A.k.a. Messaging
@jeppec
Smart pipes and “dumb” endpoints – Push protocol
Service X
Publish
Topic
Data and flow direction
Message infrastructure
(e.g. Kafka, RabbitMQ, QBus)
Service Z
Subscribe
Service Y
Subscribe
Service M
Subscribe
@jeppec
Service X
Publish
Service Z
Consume
Service Y
Consume
Service M
Consume
Atom File based Feed
generator
Oldest
Feed
Older
Feed
Old
Feed
Feed
Head Previous feed link
HTTPEndpoint
Next feed link
Next feed link
Previous feed link
HTTP Reverse
Proxy
HTTP Reverse
Proxy
Cache
Cache
Atom Pub Feed
as a JSON/XML
file
Dumb pipes and smart endpoints (Pull Protocol)
Data and flow direction
@jeppec
Business Event as XML Message
<OrderWasAccepted>
<CustomerId>50D1F244-ABBC-4EC7-BDCA-E4934C124A89</CustomerId>
<OrderId>C199322A-01F1-4E56-918E-7A63529F8FA3</OrderId>
<ShippingAddress> ... </ShippingAddress>
<BillingAddress> ... </BillingAddress>
<Items>
<Item ProductId="4CD22C4B-600C-4477-B5BF-48ABDEE4DA61" Amount="100"
AmountUnit="Pieces" UnitPrice="100,10" UnitCurrency="EUR"/>
<Item ProductId="56E6BD19-660C-464A-9120-100DAF579855" Amount="10"
AmountUnit="Litres" UnitPrice="56,95" UnitCurrency="CHF"/>
</Items>
</OrderWasAccepted>
@jeppec
Business Event as JSON Message
{
EventType: "OrderWasAccepted",
CustomerId: "50D1F244-ABBC-4EC7-BDCA-E4934C124A89",
OrderId: "C199322A-01F1-4E56-918E-7A63529F8FA3",
ShippingAddress: { ... }
BillingAddress: { ... }
Items: [
{
ProductId: "4CD22C4B-600C-4477-B5BF-48ABDEE4DA61",
Amount: "100",
AmountUnit: "Pieces",
UnitPrice: "100,10",
UnitCurrency: "EUR"
},
{
ProductId: "56E6BD19-660C-464A-9120-100DAF579855",
Amount: "10",
AmountUnit: "Litres",
UnitPrice: "56,95",
UnitCurrency: "CHF"
}
]
}
@jeppec
Different uses for Events
• Master data management and Data duplication
• Data replication / migration
• Notification
• Service autonomy and Loosely coupled workflows/business processes
across Services
• UI component integration
@jeppec
Master data management
@jeppec
System A
System
B
System
C
System
D
4 systems with shared data and no clear master for data
@jeppec
Login
Contact
Information
Memberships
CustomerMaster
System A
System
B
System
C
System
D
New solution
@jeppec
System A
Update Customer
Contact Info
Update
Customer
ContactInfo
Process
”UpdateCustomerContactInfo”
command
Event Publisher
”CustomerContactInfoUpdated” Event
Customer View
Customer
local/cached replica
System D
BI/And other parties
System B
Customer Master
@jeppec
Event channel based migration
New UI Old UI
Command Action
SQL
D
E
<<Read replica>>
C
Publishes Events
Event Publisher Channel
SQL
A
B
C
@jeppec
Example of Event based notification
TrainPositionChanged
TrainStopped
TrainStarted
…
Every 5 ms.
Subscriber A
Subscriber B
Subscriber C
@jeppec
Business Processes
@jeppec
Eventual consistency
• Consistency is with each Service
• Eventual consistency is between Services
• Like in the real world
Sales Invoicing
Inventory Shipping
Order
Accepted
Invoice Customer
Checks Inventory for availability Books truck driver
Order
Packaged
Fetch Package from Inventory
Customer
Invoiced
@jeppec
Important!
Figure out who owns the process
@jeppec
Gain competitive advantage
From vertical integration
@jeppec
Choreographed Event Driven Processes
Sales Service
Order
Accepted
Billing Service
Order Fulfilment
(Saga/
Process-Manager)
Shipping Service
Online Ordering System
MessageChannel(e.g.aTopic)
Order
Accepted
Order
Accepted
Customer
Billed
Customer
Billed
Order
Approved
Order
Approved
Works as a Finite
State Machine
(WorkFlow)
handling the life
cycle of Shipping and
thereby forms a very
central new
Aggregate in the
System
@jeppec
Process Managers
• Process Managers are essential to the coordination and
monitoring of long running business
processes/transactions
• They work as a Finite State Machines (WorkFlow) which
handling the life cycle of Process (e.g. Shipping an Order)
and thereby forms a very central new Aggregate in the
System
• They can include manual steps/person intervention
• Sometimes these Process Managers belong naturally
within a specific Business capability and other times they
are truly and thing by themselves and often form new
business capabilities that way
Many companies derive their competitive advantages from their Processes.
A Process Manager allows you coordinate Business Processes on the basis of Events
@jeppec
Back to our example
@jeppec
Choreographed Event Driven Processes
Order Service
Order
Accepted
Inventory Service
Shipping Service
Online Ordering System
MessageChannel(e.g.aTopic)
Order
Accepted
Inventory
Products
Reserved
Inventory
Products
Reserved
Order Ready
For
Shipping
Order
Ready For
Shipping
@jeppec
Accept Order flow
public class OrderService {
public void handle(AcceptOrder cmd) {
orderRepository.add(new Order(cmd.orderId,
cmd.orderLines));
publish(new OrderAccepted(
cmd.orderId,
cmd.orderLines.toMap(OrderLine::productId,
OrderLine::quantity))
);
}
@jeppec
Inventory flow
public class InventoryService {
public void on(OrderAccepted e) {
List<ProductId> productIdsOutOfStock = reserveProductsForOrder(e.orderId,
e.productsToReserve);
if (productIdsOutOfStock.isEmpty()) {
publish(new InventoryProductsReserved(e.orderId, e.productsToReserve));
} else {
publish(new InventoryProductsOutOfStock(e.orderId, e.productsToReserve,
productIdsOutOfStock));
}
}
@jeppec
Message Handling and Idempotence
public class OrderShippingProcess {
private void on(OrderPaid orderPaid, ProductsReserved productsReserved) {
ShippingDetails shippingDetails = getShippingDetailsForOrder(orderPaid.orderId);
….
printShippingLabel(orderPaid.orderId, shippingDetails.address);
}
…
} Must also be idempotent
@jeppec
There are only two hard
problems in distributed
systems
2. Exactly Once Delivery
1. Guaranteed Order of Messages
2. Exactly Once Delivery
@mathiasverraes
@jeppec
Things are not quite the same
In a distributed systems the order in which messages arrive is not
guaranteed
In a distributed systems message delivery can and will fail!
Messages can depending on guarantees be delivered:
• At Most Once – If you don’t care about loosing messages
• Page visits
• Ad views
• Other types of notifications
• Exactly Once
• Not really possible
• At Least Once
• For everything else – which is why:
Everything that can handle messages must be built with idempotency in mind!
@jeppec
Event coordination
• Do NOT copy data from incoming event to outgoing event – instead use
Event coordination from the next slide
Service C Event C
Data:
X
Data: Y
Data:
C, Y
Service D
This form of data copying from incoming
event to outgoing event is prohibited
@jeppec
Event coordination continued
• Rule: If Service D also needs data from Event B, then Service D should
also listen for Event B instead of having Service C copy data from Event
B to Event C
Service C Event C
Data:
X
Data: Y
Data:
C
Service
D
Data: Y
@jeppec
What should our Events look like?
• Keep Events Small
• Only include relevant metadata (such as sequence) and business data
• If possible, don’t include unnecessary id’s for aggregates in other services
• Think Inverse relationships based on what feels most natural
• Order doesn’t need to know the Shipping Id, but Shipping can know the Order Id
• Anchor Events to Time
• Include information about how long you can trust the Event’s change
• E.g. a price is valid until 8pm tomorrow night
• Reveal the intention and time anchoring as part of the Events name
@jeppec
Internal vs External Events
{
EventType: ”OrderWasPaid",
CustomerId: "50D1F244-ABBC-4EC7-BDCA-E4934C124A89",
OrderId: "C199322A-01F1-4E56-918E-7A63529F8FA3",
Amount: …..
}
Internal Event:
{
EventType: "OrderWasPaid",
OrderId: "C199322A-01F1-4E56-918E-7A63529F8FA3"
}
External Event:
@jeppec
Avoid using Events for generic services
When ever a new Service is introduced
the Generic Service (X) needs to be changed.
This creates an unnecessary degree of coupling
Service
A
Service
B
Service
C
Service X
Service
D
Event X
@jeppec
Use Commands for generic services
Instead of having Service X listen for MANY different events, let
Service X expose a command interface that other services can
use instead
Service
A
Service
B
Service
C
Service X
Service
D
Event X
@jeppec
When to prefer a Pull based model?
• You want to have Authority (as opposed to Autonomy)
• Such as with a bank account
• GDPR compliance
• So sensitive data is only stored in one place (avoid data duplication)
• Orchestration of processes
• Sometimes events result in too much ceremony
• You have large amounts of data and your queries only involve a small set of
this data
• Example a Google search
• Most UI to backend interactions
• Loading data from the backend
• Pressing a button to send a Query or issue a Command
@jeppec
How does shipping know
where to ship items to?
@jeppec
Check out process
@jeppec
Order Id
• Customer/customer-id
@jeppec
Order Id
• Customer/customer-id
Linked to Order Id:
• Order Delivery address
• Order Shipping method
Linked to Order Id:
• Order shipping price
@jeppec 91
Order
OrderId
Total
DeliveryAddress
Status
…
Decomposing the domain
Order
OrderId
Total
Status
…
ShippingDetails
OrderId
DeliveryAddress
@jeppec
Composite page example
Page Context:
{id: ISBN-10 0-321-83457-7 }
Images
Books
Reviews
Pricing
Inventory
OthersAlsoBought
Pricing
Reviews
Books
Images
Books
@jeppec
Service
Service - 1
Service - 2Service - 2
Service - 3
Service - 3Service - 4
Broker
Broker
(Kafka,
RabbitMQ,
JMS)
Service - 1Service - 1
Service - 1
Service - 2
Service - 2
Service - 3
Service - 3
Service - 4
Service - 4
Service - 4
@jeppec
Service
Service - 1
Service - 1Service - 1
Service - 1
Service - 2
Service - 2
Service - 2
Service - 2
Service - 3
Service - 3
Service - 3
Service - 3
Service - 4
Service - 4
Service - 4
Service - 4
QBus
@jeppec
pricing_engine_ac
(deployed on 10.25.26.102)
pricing_engine_ac
(deployed on 10.25.26.101)
Bus Bus
Bus Bus
inventory_ac
(deployed on 10.25.26.104)
inventory_ac
(deployed on 10.25.26.103)
Federated Bus
@jeppec
Topics
Bus features
• Decouples publisher from subscribers
• Provides temporal decoupling
• If a subscriber is unavailable it will receive its messages when it comes
online
Service - 4
Service - 1
Service - 3
@jeppec
Topics
CreateOrder
Sales_Service:Orders_Ac
Webshop Application
Customer_Service:Some_Ac
OrderCreated
OrderCreatedEvent
Publish
CreateSomeRelatedAggregate
Sales_Service:OrderEvents
<<Topic>>
OrderCreatedEvent
@jeppec
Topics
Bus features
But how do we handle:
• Coding errors in Subscribers?
• New Subscribers that didn’t exist when the events were originally
published?
Service - 4
Service - 1
Service - 3
@jeppec
@jeppec
1. Realization
Commands and Queries
support very different usecases
@jeppec
A single model cannot be
appropriate for reporting,
searching and transactional
behavior
Greg Young
@jeppec
The Triangular Architecture
Task based
UI
Write
ModelRead ModelRead Model
Read
Models
@jeppec
Commands & Events
Commands mutate Write model state
which results in
one or more Events being published.
Command Event(s)
AcceptOrder OrderAccepted
ShipOrder OrderShipped
AddComment CommentAdded
QuarantineReview ReviewQuarantined
UnquarantineReview ReviewUnquarantined
@jeppec
The circle is completing
@jeppec
We can use Domain Events to build our
View/Query models
@jeppec
Read model
Read model
Commands, Events and Query Models
Events
UI
Write modelView models
”AcceptOrder”
command
”OrderAccepted”
event
”Find all Accepted Orders”
Query
Commands are Imperative: DoStuff
Events are Past tense: StuffDone
@jeppec
CQRS Building blocks
Client
Commands
Command
Bus
Sends
Command
Handlers
Modify
Repositories
Read Write
Data
store
Event
Bus
Event Handlers
Events
Read store
Query HandlersQuery Results
Queries
Events
Domain
@jeppec
Event Sourcing
Write-Model/Aggregates track their own Domain Events
and derive state from them
Time
07:39
Time
07:40
Time
07:41
Time
07:45
Time
07:46
Time
07:50
CRUD
There’s no reason to delete data or update data.
We only need to append and read Events from our Event Store
@jeppec
Event Replaying
Type Aggregate
Identifier
Sequence
Number
Timestamp Event
Identifier
EventType SerializedEvent
Order 14237 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>…
Order 14237 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>…
Order 14237 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>…
Order 14237 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>…
Order 14237 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>…
Order 14237 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>…
Order
Accepted: true
Orderline
Orderline
@jeppec
Full CQRS with EventSourcing
UI Domain
Event
Store
Commands – Change data
Commands Events
SQL DB Document DB Graph DB
UI Data
Queries – Ask for data
Events
Query Build
Our single source
of truth
@jeppec
And we can use the
EventStore to support event
replays for new subscribers
@jeppec
Client handled subscriptions
• Highly resilient pattern for an Event Driven Architecture that’s backed by
Event-Sourced services
• In this model the publisher of the Events is responsible for the durability of
all its Events, typically to an EventStore/EventLog.
• Each client (subscriber) maintains durable information of the last event it has
received from each publisher.
• When ever the client starts up it makes a subscription to the publisher
where it states from which point in time it wants events published/streamed
to it.
• This effectively means that publisher can remain simple and the client
(subscriber) can remain simple and we don’t need additional sophisticated
broker infrastructure such as Kafka+ZooKeeper.
@jeppec
Client handled subscriptions
Publisher
Subscriber A
Local storage
EventStore
Subscriber B
Local storage
Topic
Subscription
Topic
Subscription
TopicSubscriptionHandler
TopicSubscriptionHandler
EventEvent
Event Event
EventBus
Event
Event
Distributed Event Bus,
which ensures that
live events published
on an AC node in the
cluster can be seen
by all AC’s of the
same type
Singe Instance
Subscriber, which
ensures that only
one instance of
Subscriber B has
an active
subscription(s).
Other instances of
the same
subscriber are
hot-standby
<<Topic Subscriber>>
Customer_Service:Some_Ac:OrderEvents
<<Topic Publisher>>
Sales_Service:OrderEvents
@jeppec
Topics
Bus features
Service - 4
Service - 1
Service - 3
bus.registerReplayableTopicPublisher(InternalPricingEvents.TOPIC_NAME,
replayFromAggregate(Pricing.class)
.dispatchAggregateEventsOfType(
InternalPricingEvents.class
)
);
bus.subscribeTopic(SERVICE_AC_ID.topicSubscriber(”Pricing"),
InternalPricingEvents.TOPIC_NAME,
new PricingTopicSubscription(bus));
@jeppec
Topics
public class PricingTopicSubscription extends BusMessageHandlerDelegator {
@BusMessagePayloadHandler
private void on(PrincingChanged e, BusMessage<PricingChanged> msg) {
…
}
…
}
@jeppec
Topics
Bus features
Features:
• The Bus provides automatic and durable handling of Redelivery in case of message handling failure
through a Redelivery Policy
• Exponential Back-off
• Max retries
• Dead letter/Error Queue
• Support for resubscription at any point in the timeline of an Event Stream
• Automatically tracking of resubscription points - aka. resubscribe at last tracked point
Service - 4
Service - 1
Service - 3
@jeppec
Can’t we just use Kafka?
@jeppec
With Kafka we still need smart
endpoints
@jeppec
Requires a lot of work
@jeppec
Bus features
Features:
• Support for sending a message to a single consumer
• Default pattern is Competing Consumers
• The bus provides durability for all messages sent on a Queue
• The Bus provider automatic and durable handling of Redelivery in case of message handling failure
through a Redelivery Policy
• Exponential Backoff
• Max retries
• Dead letter/Error Queue
Durable Queues
@jeppec
Can’t we just use Kafka?
@jeppec
Bus features
Notifications:
• Durable notifications with failover support
• bus.notify(notificationQueue, notificationMessage)
Notifications
Distributed
Broadcast
Broadcast:
• Broadcast a Message to all Services in the cluster
• Broadcast a Message to a UI client (all, per user, per privilege)
@jeppec
Bus features
Single Instance Task:
• Ensures that only one Active instance of a Task is active in the cluster at one time
• Other tasks of the same type are in hot standby
• Used to e.g. group multiple subscribers, to ensure that all subscribers are either all
active or standby.
• Used by our ViewRepositories
Distributed
SingleInstanceTask
bus.createClusterSingleInstanceTask(”MyTask",
new MyTask ()); // Where MyTask implements Lifecycle
@jeppec
Bus features
Process Manager
• Defines durable business processes
as a flow of Events
Sagas
Process Manager
Sales Service
Order Accepted
Billing Service
Order Fulfilment
(Saga/
Process-Manager)
Shipping Service
MessageChannel(e.g.aTopic)
Order Accepted
Order Accepted
Customer Billed
Customer Billed
Order Approved
Order Approved
@jeppec
Application features
Workflow Manager:
• Defines the Tasks that required human intervention, such as:
• Approvals (e.g. Contract approval)
• Assistance/Help with a business problem
• Incident handling (e.g. a technical problem identified by a developer)
• Authorization (e.g. request manager approval)
• Reminders
• Common tasks supported: Claiming Tasks, Escalating Tasks, Completing Tasks
Workflow Manager
@jeppec
Event Monitoring
• One of the challenges with have Event based systems is to keep track
of where events are in the flow
• Why isn’t this system doing this?
• Did events go into an Error Queue?
• How can we reprocess them?
• Or why isn’t this happening?
• In general it’s the same issues we see with asynchronous systems and
integrations in general.
• The good thing about using Events is that they carry semantics and are not RPC
in hiding
@jeppec
Event Monitoring
Command, Document and Event messages all contain the following
data:
• Timestamp
• Unique message Id
• Correlation Id
• Originating system (IP address, name, etc.)
• …
Event messages also need to describe the order in which the event
occurred. This can be implemented in many ways including:
• Ordering sequence number
• Event vectors
• Vector clocks
• Timestamp
• Etc.
@jeppec
Event Monitoring
• We use the correlation id to be able to later correlate log
messages from different services
@jeppec
Example monitoring of Processes
Thanks :)
Blog: https://cramonblog.wordpress.com/
Homepage: http://cloudcreate.dk/
Twitter: @jeppec

Should you be using an event driven architecture?

  • 1.
    Should you beusing an Event Driven Architecture? Jeppe Cramon - @jeppec Cloud Create ApS
  • 2.
  • 3.
  • 4.
    @jeppec “Example isn’t anotherway to teach, it is the only way to teach” Albert Einstein
  • 5.
    @jeppec Let’s look atOnline Shopping
  • 6.
    @jeppec Most sites followa similar pattern
  • 7.
  • 8.
    @jeppec public class OrderService{ public void save(Order order) { orderRepository.add(order); if (inventoryService.areOrderItemsInStock(order)) { order.setStatus(OrderStatus.ItemsInStock); shippingService.startPackagingOrder(order); emailService.send(generateOrderOrderItemsInStockEmail(order)); } else { order.setStatus(OrderStatus.NotAllItemsInStock); emailService.send(generateOrderItemsNotInStockEmail(order)); executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1)); } } public void checkOrderInventoryStatus(OrderId orderId) { Order order = orderRepository.load(orderId); if (inventoryService.areOrderItemsInStock(order)) { order.setStatus(OrderStatus.ItemsInStock); shippingService.startPackagingOrder(order); emailService.send(generateOrderOrderItemsInStockEmail(order)); } else { executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1)); } } Side effect. What happens if we call save() twice?
  • 9.
    @jeppec public class OrderService{ public void save(Order order) { orderRepository.add(order); if (inventoryService.areOrderItemsInStock(order)) { order.setStatus(OrderStatus.ItemsInStock); shippingService.startPackagingOrder(order); …
  • 10.
    @jeppec Let’s decouple Order-and Inventory Service further public class OrderService { public void save(Order order) { orderRepository.add(order); if (inventoryService.areOrderItemsInStock(order)) { order.setStatus(OrderStatus.ItemsInStock); shippingService.startPackagingOrder(order);
  • 11.
    @jeppec public class OrderService{ public void save(Order order) { orderRepository.add(order); List<ProductId> productIdsOutOfStock = inventoryService.checkProductStockStatus( order.orderLines.toMap(OrderLine::productId, OrderLine::quantity)); if (productIdsOutOfStock.isEmpty()) { order.setStatus(OrderStatus.ItemsInStock); shippingService.startPackagingOrder(order); emailService.send(generateOrderOrderItemsInStockEmail(order)); } else { order.setStatus(OrderStatus.NotAllItemsInStock); emailService.send(generateOrderItemsNotInStockEmail(order, productIdsOutOfStock)); executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1)); } }
  • 12.
  • 13.
    @jeppec public class OrderService{ public void save(Order order) { orderRepository.add(order); List<ProductId> productIdsOutOfStock = inventoryService.checkProductStockStatus( order.orderLines.toMap(OrderLine::productId, OrderLine::quantity)); if (productIdsOutOfStock.isEmpty()) { order.setStatus(OrderStatus.ItemsInStock); shippingService.startPackagingOrder(order); emailService.send(generateOrderOrderItemsInStockEmail(order)); } else { order.setStatus(OrderStatus.NotAllItemsInStock); emailService.send(generateOrderItemsNotInStockEmail(order, productIdsOutOfStock)); executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1)); } }
  • 14.
    @jeppec public class OrderService{ public void save(Order order) { orderRepository.add(order); List<ProductId> productIdsOutOfStock = inventoryService.reserveProductsForOrder(order.orderId, order.orderLines.toMap(OrderLine::productId, OrderLine::quantity)); if (productIdsOutOfStock.isEmpty()) { order.setStatus(OrderStatus.ItemsInStock); shippingService.startPackagingOrder(order); emailService.send(generateOrderOrderItemsInStockEmail(order)); } else { order.setStatus(OrderStatus.NotAllItemsInStock); emailService.send(generateOrderItemsNotInStockEmail(order, productIdsOutOfStock)); executeMethod(OrderService::checkOrderInventoryStatus, Period.ofDays(1)); } } Introduces an additional side effect
  • 15.
    @jeppec Essential complexity of2 way integration Inventory Service Order Service Shipping Service UI Start- Packaging Save-Order Order:Save-Order() call Inventory:Reserve-Products() call Shipping:Start-Packaging() commit() Reserve- Products Local transaction between the 3 “Services”
  • 16.
  • 17.
    @jeppec Accidental complexity fromdistributed service integration Inventory Service Order Service Shipping Service UI Start- Packaging Save-Order Reserve- Products Local transaction between 2 local “Services”Remote call
  • 18.
  • 19.
    @jeppec What’s the challengewith using a Pull model between distributed components?
  • 20.
  • 21.
    @jeppec Synchronous calls lowerour tolerance for faults • When you get an IO error • When servers crash or restarts • When databases are down • When deadlocks occurs in our databases • Do you retry? With synchronous style Service interaction we can loose business data if there’s no automatic retry Or we risk creating data more than once if the operation isn’t idempotent* Client Server Duplicated Response Duplicated Request Processing Response Request Processing The same message can be processed more than once *Idempotence describes the quality of an operation in which result and state does not change if the operation is performed more than 1 time
  • 22.
  • 23.
  • 24.
    @jeppec Sales system Sale Delivery system Deliveries Customer/CRM systemCustomer SAP Bookkeeping Complete Purchase Transaction Coordinator Transactional Resource Prepare Phase Commit Phase 2 Phase Commit
  • 25.
    @jeppec What’s wrong withdistributed transactions? • Transactions lock resources while active • Services are autonomous • Can’t be expected to finish within a certain time interval • Locking keeps other transactions from completing their job • Locking doesn’t scale • X Phase Commit is fragile by design
  • 26.
    @jeppec Using the pullmodel we’re prone to cascading failure
  • 27.
    @jeppec Order:Save-Order() call Inventory:Reserve-Products() call Shipping:Start-Packaging() if(Shipping:Call-Failed:Too-Busy?) Wait-A-While() call Shipping:Start-Packaging() if (Shipping:Call-Failed:Too-Busy?) Wait-A-Little-While-Longer() call Shipping:Start-Packaging() if (Shipping:Call-Failed:IO-Error?) Save-We-Need-Check-If-Call-Shipping-Succeded-After-All AND We-Need-To-Retry call Order:Save-Order and call Inventory:Reserve-Products AND Tell-Customer-That-This-Operation-Perhaps-Went-Well if (Shipping:Call-Went-Well?) commit() Accidental complexity from distributed service integration Inventory Service Order Service Shipping Service UI Start- Packaging Save-Order Reserve- Products Local transaction between 2 Components
  • 28.
    @jeppec “A distributed systemis one where a machine I’ve never heard of can cause my program to fail” — Leslie Lamport
  • 29.
    @jeppec Service autonomy Order Inventory Shipping RetailSystem Shipping Order Inventory Retail System Slow/unreliable network Different SLA Slow system
  • 30.
    @jeppec Availability goes down (withoutadditional instances of each service) Combined availability: 97%
  • 31.
  • 32.
    @jeppec But surely RPCis the only way to do this?
  • 33.
    @jeppec We need toswitch from a pull to a push model Which means we need to change our approach slightly but profoundly
  • 34.
    @jeppec Most applications arebuilt with CRUD as the main paradigm Intention gets lost
  • 35.
    @jeppec We need toshift focus from pure data towards intent and process automation This means a change from CRUD style application design, where the process was implicit and stored in the minds of the users First I need to enter the employee in this screen Then I need to press a button in the user system so they’re properly created And the I need to add their access card to this screen and press Activate
  • 36.
    @jeppec We need tocapture User Intent at the UI CRUD style Task based style Intent
  • 37.
    @jeppec Task-based/Inductive UI • TraditionalCRUD UI is what I would call a WHAT UI • Task based UI’s focuses on HOW the user wants to use the application • Guides users through the work process • The UI becomes an intrinsic part of the design • The UI design directly affects our commands and thereby our transactional boundaries
  • 38.
    @jeppec Capturing intent inthe form of a Command A Command is prescriptive of what should happen, and its primary goal is to capture USER INTENT A Command supports a single usecase and targets a single business object within a single Transaction Commands always carry a name in its imperative form: • AcceptOrder • ShipOrder • CancelOrder • ReimburseCustomer • Etc. “A command describes a Task that you want someone else to carry out for you and where the recipient can choose to reject the Command”
  • 39.
    @jeppec Let’s capture theintent of the customer public class OrderService { public void handle(AcceptOrder cmd) { orderRepository.add(new Order(cmd.orderId, cmd.orderLines)); ???? }
  • 40.
    @jeppec How do weget the ball moving now that we can’t use RPC?
  • 41.
    @jeppec Life Beyond DistributedTransactions by Pat Helland 1. How do we split our data 2. How do we identify our data 3. How do we communicate between our services
  • 42.
    @jeppec 1. How dowe split our data Data must be collected in pieces called aggregates. These aggregates should be limited in size (but not smaller), so that, after a transaction they are consistent. Rule of thumb: One transaction involves only one aggregate.
  • 43.
    @jeppec Domain Driven Design Theterm Aggregate comes from DDD
  • 44.
    @jeppec Aggregates Invoice InvoiceLine * Account * What: • Clustercoherent Entities and Value Objects, with complex associations into Aggregates with well defined boundaries. • Choose one entity to be root and control access to objects inside the boundary through the root. Motivation: Control invariants and consistency through the aggregate root. Ensuring consistency & transactional boundaries for Distributed scenarios! Root * *
  • 45.
    @jeppec 2. How dowe identify our data According to Pat Helland we need to be able to uniquely identify each Aggregate using an ID. • This ID will usually a UUID/GUID {21EC2020-3AEA-4069-A2DD-08002B30309D} 2122 (approximately 5.3×1036) combinations
  • 46.
    @jeppec Aggregates refer toeach other by ID they NEVER use memory pointers, join tables or remote calls
  • 47.
    @jeppec Services/Bounded Contexts andAggregates Order Product Customer customerId … Order orderId customerId … OrderLine orderId productId quantity price ProductCategory productCategoryId … Product productId productCategoryId name tag ...
  • 48.
    @jeppec 3. How dowe communicate between our services What do we do when our process involves more than one aggregate and therefore likely more than one service?
  • 49.
    @jeppec Synchronous calls arethe crystal meth of programming At first you make good progress but then the sheer horror becomes evident when you realise the scalability limitations and how the brittleness holds back both performance and development flexibility. By then it is too late to save. http://www.infoq.com/news/2014/10/thompson-reactive-manifesto-2 We need the reactive properties and then apply protocols for the message interactions. Without considering the protocols of interaction this world of micro-services will become a coordination nightmare. Martin Thompson
  • 50.
  • 51.
    @jeppec Coupling matrix* * Modifiedversion of Ian Robinson’s matrix: http://iansrobinson.com/2009/04/27/temporal-and-behavioural-coupling/ Behavioral coupling Temporal coupling Low High Low High Reactive/ Event oriented Command oriented Emergency services Distributed 3 layer
  • 52.
    @jeppec We need tochange focus from short technical transactions To long running business transactions supporting business processes
  • 53.
    @jeppec Using Business Eventsto drive Business Processes Order Service Shipping Billing Sales Customers MessageChannel Online Ordering System Web Shop (Composite UI) Inventory Service Shipping Service Order Accepted AcceptOrder The sales fulfillment processing can now begin…
  • 54.
    @jeppec Events An Event isnon-prescriptive of what should happen in other parts of the system. An event will typically be published to multiple consumers/subscribers: • The publisher of the event does not know who the subscribers are • And the publisher doesn’t know what the subscribers intend to do with the event Events always carry a name in its past-tense form: OrderWasAccepted OrderHasShipped CustomerWasReimbursed “An Event describes something that HAS happened”
  • 55.
    @jeppec Push model • Eventbased integration follows a push based model that breaks temporal coupling and avoids cascading failures • Events can be exchanged between services over a message channel (that can be implemented using a push or pull protocol) Sender Address Changed Event Receiver Address Changed Event Channel Asynchronous Communication – A.k.a. Messaging
  • 56.
    @jeppec Smart pipes and“dumb” endpoints – Push protocol Service X Publish Topic Data and flow direction Message infrastructure (e.g. Kafka, RabbitMQ, QBus) Service Z Subscribe Service Y Subscribe Service M Subscribe
  • 57.
    @jeppec Service X Publish Service Z Consume ServiceY Consume Service M Consume Atom File based Feed generator Oldest Feed Older Feed Old Feed Feed Head Previous feed link HTTPEndpoint Next feed link Next feed link Previous feed link HTTP Reverse Proxy HTTP Reverse Proxy Cache Cache Atom Pub Feed as a JSON/XML file Dumb pipes and smart endpoints (Pull Protocol) Data and flow direction
  • 58.
    @jeppec Business Event asXML Message <OrderWasAccepted> <CustomerId>50D1F244-ABBC-4EC7-BDCA-E4934C124A89</CustomerId> <OrderId>C199322A-01F1-4E56-918E-7A63529F8FA3</OrderId> <ShippingAddress> ... </ShippingAddress> <BillingAddress> ... </BillingAddress> <Items> <Item ProductId="4CD22C4B-600C-4477-B5BF-48ABDEE4DA61" Amount="100" AmountUnit="Pieces" UnitPrice="100,10" UnitCurrency="EUR"/> <Item ProductId="56E6BD19-660C-464A-9120-100DAF579855" Amount="10" AmountUnit="Litres" UnitPrice="56,95" UnitCurrency="CHF"/> </Items> </OrderWasAccepted>
  • 59.
    @jeppec Business Event asJSON Message { EventType: "OrderWasAccepted", CustomerId: "50D1F244-ABBC-4EC7-BDCA-E4934C124A89", OrderId: "C199322A-01F1-4E56-918E-7A63529F8FA3", ShippingAddress: { ... } BillingAddress: { ... } Items: [ { ProductId: "4CD22C4B-600C-4477-B5BF-48ABDEE4DA61", Amount: "100", AmountUnit: "Pieces", UnitPrice: "100,10", UnitCurrency: "EUR" }, { ProductId: "56E6BD19-660C-464A-9120-100DAF579855", Amount: "10", AmountUnit: "Litres", UnitPrice: "56,95", UnitCurrency: "CHF" } ] }
  • 60.
    @jeppec Different uses forEvents • Master data management and Data duplication • Data replication / migration • Notification • Service autonomy and Loosely coupled workflows/business processes across Services • UI component integration
  • 61.
  • 62.
    @jeppec System A System B System C System D 4 systemswith shared data and no clear master for data
  • 63.
  • 64.
    @jeppec System A Update Customer ContactInfo Update Customer ContactInfo Process ”UpdateCustomerContactInfo” command Event Publisher ”CustomerContactInfoUpdated” Event Customer View Customer local/cached replica System D BI/And other parties System B Customer Master
  • 65.
    @jeppec Event channel basedmigration New UI Old UI Command Action SQL D E <<Read replica>> C Publishes Events Event Publisher Channel SQL A B C
  • 66.
    @jeppec Example of Eventbased notification TrainPositionChanged TrainStopped TrainStarted … Every 5 ms. Subscriber A Subscriber B Subscriber C
  • 67.
  • 68.
    @jeppec Eventual consistency • Consistencyis with each Service • Eventual consistency is between Services • Like in the real world Sales Invoicing Inventory Shipping Order Accepted Invoice Customer Checks Inventory for availability Books truck driver Order Packaged Fetch Package from Inventory Customer Invoiced
  • 69.
  • 70.
  • 71.
    @jeppec Choreographed Event DrivenProcesses Sales Service Order Accepted Billing Service Order Fulfilment (Saga/ Process-Manager) Shipping Service Online Ordering System MessageChannel(e.g.aTopic) Order Accepted Order Accepted Customer Billed Customer Billed Order Approved Order Approved Works as a Finite State Machine (WorkFlow) handling the life cycle of Shipping and thereby forms a very central new Aggregate in the System
  • 72.
    @jeppec Process Managers • ProcessManagers are essential to the coordination and monitoring of long running business processes/transactions • They work as a Finite State Machines (WorkFlow) which handling the life cycle of Process (e.g. Shipping an Order) and thereby forms a very central new Aggregate in the System • They can include manual steps/person intervention • Sometimes these Process Managers belong naturally within a specific Business capability and other times they are truly and thing by themselves and often form new business capabilities that way Many companies derive their competitive advantages from their Processes. A Process Manager allows you coordinate Business Processes on the basis of Events
  • 73.
  • 74.
    @jeppec Choreographed Event DrivenProcesses Order Service Order Accepted Inventory Service Shipping Service Online Ordering System MessageChannel(e.g.aTopic) Order Accepted Inventory Products Reserved Inventory Products Reserved Order Ready For Shipping Order Ready For Shipping
  • 75.
    @jeppec Accept Order flow publicclass OrderService { public void handle(AcceptOrder cmd) { orderRepository.add(new Order(cmd.orderId, cmd.orderLines)); publish(new OrderAccepted( cmd.orderId, cmd.orderLines.toMap(OrderLine::productId, OrderLine::quantity)) ); }
  • 76.
    @jeppec Inventory flow public classInventoryService { public void on(OrderAccepted e) { List<ProductId> productIdsOutOfStock = reserveProductsForOrder(e.orderId, e.productsToReserve); if (productIdsOutOfStock.isEmpty()) { publish(new InventoryProductsReserved(e.orderId, e.productsToReserve)); } else { publish(new InventoryProductsOutOfStock(e.orderId, e.productsToReserve, productIdsOutOfStock)); } }
  • 77.
    @jeppec Message Handling andIdempotence public class OrderShippingProcess { private void on(OrderPaid orderPaid, ProductsReserved productsReserved) { ShippingDetails shippingDetails = getShippingDetailsForOrder(orderPaid.orderId); …. printShippingLabel(orderPaid.orderId, shippingDetails.address); } … } Must also be idempotent
  • 78.
    @jeppec There are onlytwo hard problems in distributed systems 2. Exactly Once Delivery 1. Guaranteed Order of Messages 2. Exactly Once Delivery @mathiasverraes
  • 79.
    @jeppec Things are notquite the same In a distributed systems the order in which messages arrive is not guaranteed In a distributed systems message delivery can and will fail! Messages can depending on guarantees be delivered: • At Most Once – If you don’t care about loosing messages • Page visits • Ad views • Other types of notifications • Exactly Once • Not really possible • At Least Once • For everything else – which is why: Everything that can handle messages must be built with idempotency in mind!
  • 80.
    @jeppec Event coordination • DoNOT copy data from incoming event to outgoing event – instead use Event coordination from the next slide Service C Event C Data: X Data: Y Data: C, Y Service D This form of data copying from incoming event to outgoing event is prohibited
  • 81.
    @jeppec Event coordination continued •Rule: If Service D also needs data from Event B, then Service D should also listen for Event B instead of having Service C copy data from Event B to Event C Service C Event C Data: X Data: Y Data: C Service D Data: Y
  • 82.
    @jeppec What should ourEvents look like? • Keep Events Small • Only include relevant metadata (such as sequence) and business data • If possible, don’t include unnecessary id’s for aggregates in other services • Think Inverse relationships based on what feels most natural • Order doesn’t need to know the Shipping Id, but Shipping can know the Order Id • Anchor Events to Time • Include information about how long you can trust the Event’s change • E.g. a price is valid until 8pm tomorrow night • Reveal the intention and time anchoring as part of the Events name
  • 83.
    @jeppec Internal vs ExternalEvents { EventType: ”OrderWasPaid", CustomerId: "50D1F244-ABBC-4EC7-BDCA-E4934C124A89", OrderId: "C199322A-01F1-4E56-918E-7A63529F8FA3", Amount: ….. } Internal Event: { EventType: "OrderWasPaid", OrderId: "C199322A-01F1-4E56-918E-7A63529F8FA3" } External Event:
  • 84.
    @jeppec Avoid using Eventsfor generic services When ever a new Service is introduced the Generic Service (X) needs to be changed. This creates an unnecessary degree of coupling Service A Service B Service C Service X Service D Event X
  • 85.
    @jeppec Use Commands forgeneric services Instead of having Service X listen for MANY different events, let Service X expose a command interface that other services can use instead Service A Service B Service C Service X Service D Event X
  • 86.
    @jeppec When to prefera Pull based model? • You want to have Authority (as opposed to Autonomy) • Such as with a bank account • GDPR compliance • So sensitive data is only stored in one place (avoid data duplication) • Orchestration of processes • Sometimes events result in too much ceremony • You have large amounts of data and your queries only involve a small set of this data • Example a Google search • Most UI to backend interactions • Loading data from the backend • Pressing a button to send a Query or issue a Command
  • 87.
    @jeppec How does shippingknow where to ship items to?
  • 88.
  • 89.
  • 90.
    @jeppec Order Id • Customer/customer-id Linkedto Order Id: • Order Delivery address • Order Shipping method Linked to Order Id: • Order shipping price
  • 91.
    @jeppec 91 Order OrderId Total DeliveryAddress Status … Decomposing thedomain Order OrderId Total Status … ShippingDetails OrderId DeliveryAddress
  • 92.
    @jeppec Composite page example PageContext: {id: ISBN-10 0-321-83457-7 } Images Books Reviews Pricing Inventory OthersAlsoBought Pricing Reviews Books Images Books
  • 94.
    @jeppec Service Service - 1 Service- 2Service - 2 Service - 3 Service - 3Service - 4 Broker Broker (Kafka, RabbitMQ, JMS) Service - 1Service - 1 Service - 1 Service - 2 Service - 2 Service - 3 Service - 3 Service - 4 Service - 4 Service - 4
  • 95.
    @jeppec Service Service - 1 Service- 1Service - 1 Service - 1 Service - 2 Service - 2 Service - 2 Service - 2 Service - 3 Service - 3 Service - 3 Service - 3 Service - 4 Service - 4 Service - 4 Service - 4 QBus
  • 96.
    @jeppec pricing_engine_ac (deployed on 10.25.26.102) pricing_engine_ac (deployedon 10.25.26.101) Bus Bus Bus Bus inventory_ac (deployed on 10.25.26.104) inventory_ac (deployed on 10.25.26.103) Federated Bus
  • 97.
    @jeppec Topics Bus features • Decouplespublisher from subscribers • Provides temporal decoupling • If a subscriber is unavailable it will receive its messages when it comes online Service - 4 Service - 1 Service - 3
  • 98.
  • 99.
    @jeppec Topics Bus features But howdo we handle: • Coding errors in Subscribers? • New Subscribers that didn’t exist when the events were originally published? Service - 4 Service - 1 Service - 3
  • 100.
  • 101.
    @jeppec 1. Realization Commands andQueries support very different usecases
  • 102.
    @jeppec A single modelcannot be appropriate for reporting, searching and transactional behavior Greg Young
  • 103.
    @jeppec The Triangular Architecture Taskbased UI Write ModelRead ModelRead Model Read Models
  • 104.
    @jeppec Commands & Events Commandsmutate Write model state which results in one or more Events being published. Command Event(s) AcceptOrder OrderAccepted ShipOrder OrderShipped AddComment CommentAdded QuarantineReview ReviewQuarantined UnquarantineReview ReviewUnquarantined
  • 105.
  • 106.
    @jeppec We can useDomain Events to build our View/Query models
  • 107.
    @jeppec Read model Read model Commands,Events and Query Models Events UI Write modelView models ”AcceptOrder” command ”OrderAccepted” event ”Find all Accepted Orders” Query Commands are Imperative: DoStuff Events are Past tense: StuffDone
  • 108.
    @jeppec CQRS Building blocks Client Commands Command Bus Sends Command Handlers Modify Repositories ReadWrite Data store Event Bus Event Handlers Events Read store Query HandlersQuery Results Queries Events Domain
  • 109.
    @jeppec Event Sourcing Write-Model/Aggregates tracktheir own Domain Events and derive state from them Time 07:39 Time 07:40 Time 07:41 Time 07:45 Time 07:46 Time 07:50
  • 110.
    CRUD There’s no reasonto delete data or update data. We only need to append and read Events from our Event Store
  • 111.
    @jeppec Event Replaying Type Aggregate Identifier Sequence Number TimestampEvent Identifier EventType SerializedEvent Order 14237 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>… Order 14237 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>… Order 14237 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>… Order 14237 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>… Order 14237 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>… Order 14237 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>… Order Accepted: true Orderline Orderline
  • 112.
    @jeppec Full CQRS withEventSourcing UI Domain Event Store Commands – Change data Commands Events SQL DB Document DB Graph DB UI Data Queries – Ask for data Events Query Build Our single source of truth
  • 113.
    @jeppec And we canuse the EventStore to support event replays for new subscribers
  • 114.
    @jeppec Client handled subscriptions •Highly resilient pattern for an Event Driven Architecture that’s backed by Event-Sourced services • In this model the publisher of the Events is responsible for the durability of all its Events, typically to an EventStore/EventLog. • Each client (subscriber) maintains durable information of the last event it has received from each publisher. • When ever the client starts up it makes a subscription to the publisher where it states from which point in time it wants events published/streamed to it. • This effectively means that publisher can remain simple and the client (subscriber) can remain simple and we don’t need additional sophisticated broker infrastructure such as Kafka+ZooKeeper.
  • 115.
    @jeppec Client handled subscriptions Publisher SubscriberA Local storage EventStore Subscriber B Local storage Topic Subscription Topic Subscription TopicSubscriptionHandler TopicSubscriptionHandler EventEvent Event Event EventBus Event Event Distributed Event Bus, which ensures that live events published on an AC node in the cluster can be seen by all AC’s of the same type Singe Instance Subscriber, which ensures that only one instance of Subscriber B has an active subscription(s). Other instances of the same subscriber are hot-standby <<Topic Subscriber>> Customer_Service:Some_Ac:OrderEvents <<Topic Publisher>> Sales_Service:OrderEvents
  • 116.
    @jeppec Topics Bus features Service -4 Service - 1 Service - 3 bus.registerReplayableTopicPublisher(InternalPricingEvents.TOPIC_NAME, replayFromAggregate(Pricing.class) .dispatchAggregateEventsOfType( InternalPricingEvents.class ) ); bus.subscribeTopic(SERVICE_AC_ID.topicSubscriber(”Pricing"), InternalPricingEvents.TOPIC_NAME, new PricingTopicSubscription(bus));
  • 117.
    @jeppec Topics public class PricingTopicSubscriptionextends BusMessageHandlerDelegator { @BusMessagePayloadHandler private void on(PrincingChanged e, BusMessage<PricingChanged> msg) { … } … }
  • 118.
    @jeppec Topics Bus features Features: • TheBus provides automatic and durable handling of Redelivery in case of message handling failure through a Redelivery Policy • Exponential Back-off • Max retries • Dead letter/Error Queue • Support for resubscription at any point in the timeline of an Event Stream • Automatically tracking of resubscription points - aka. resubscribe at last tracked point Service - 4 Service - 1 Service - 3
  • 119.
  • 120.
    @jeppec With Kafka westill need smart endpoints
  • 121.
  • 122.
    @jeppec Bus features Features: • Supportfor sending a message to a single consumer • Default pattern is Competing Consumers • The bus provides durability for all messages sent on a Queue • The Bus provider automatic and durable handling of Redelivery in case of message handling failure through a Redelivery Policy • Exponential Backoff • Max retries • Dead letter/Error Queue Durable Queues
  • 123.
  • 124.
    @jeppec Bus features Notifications: • Durablenotifications with failover support • bus.notify(notificationQueue, notificationMessage) Notifications Distributed Broadcast Broadcast: • Broadcast a Message to all Services in the cluster • Broadcast a Message to a UI client (all, per user, per privilege)
  • 125.
    @jeppec Bus features Single InstanceTask: • Ensures that only one Active instance of a Task is active in the cluster at one time • Other tasks of the same type are in hot standby • Used to e.g. group multiple subscribers, to ensure that all subscribers are either all active or standby. • Used by our ViewRepositories Distributed SingleInstanceTask bus.createClusterSingleInstanceTask(”MyTask", new MyTask ()); // Where MyTask implements Lifecycle
  • 126.
    @jeppec Bus features Process Manager •Defines durable business processes as a flow of Events Sagas Process Manager Sales Service Order Accepted Billing Service Order Fulfilment (Saga/ Process-Manager) Shipping Service MessageChannel(e.g.aTopic) Order Accepted Order Accepted Customer Billed Customer Billed Order Approved Order Approved
  • 127.
    @jeppec Application features Workflow Manager: •Defines the Tasks that required human intervention, such as: • Approvals (e.g. Contract approval) • Assistance/Help with a business problem • Incident handling (e.g. a technical problem identified by a developer) • Authorization (e.g. request manager approval) • Reminders • Common tasks supported: Claiming Tasks, Escalating Tasks, Completing Tasks Workflow Manager
  • 128.
    @jeppec Event Monitoring • Oneof the challenges with have Event based systems is to keep track of where events are in the flow • Why isn’t this system doing this? • Did events go into an Error Queue? • How can we reprocess them? • Or why isn’t this happening? • In general it’s the same issues we see with asynchronous systems and integrations in general. • The good thing about using Events is that they carry semantics and are not RPC in hiding
  • 129.
    @jeppec Event Monitoring Command, Documentand Event messages all contain the following data: • Timestamp • Unique message Id • Correlation Id • Originating system (IP address, name, etc.) • … Event messages also need to describe the order in which the event occurred. This can be implemented in many ways including: • Ordering sequence number • Event vectors • Vector clocks • Timestamp • Etc.
  • 130.
    @jeppec Event Monitoring • Weuse the correlation id to be able to later correlate log messages from different services
  • 131.
  • 132.
    Thanks :) Blog: https://cramonblog.wordpress.com/ Homepage:http://cloudcreate.dk/ Twitter: @jeppec

Editor's Notes

  • #16 If both System A, B and C share the same technical infrastructure (e.g. database) and reside in the same memory space they can share a local transaction. In this case we’re not bound by the laws of distributed computing and everything is easy because the transaction manager will solve all our problems.
  • #21 Synchronous RPC is the crack cocaine of distributed programming
  • #26 Locking and scaling: If it takes 200 ms to carry out an operation that uses scaling, the system can maximum handle 5 concurrent users Fragility: 2,3,4 .... Phase Commit - 2PC theory concludes by saying "and this does not work in reality" - in case of error (eg. Due to timeout while waiting for a resource commit phase) you always end up with having to decide what two do "Halt and manual recovery" or guess whether it was good or bad! There are besides timeouts a big problem. At worst, timeouts last very long!
  • #35 See http://cqrs.wordpress.com/documents/task-based-ui/ for more
  • #36 CRUD = Create Read Update Delete
  • #37 There’s no intent in a Save button. In the CRUD screen you can combine multiple usecases into one (edit title, assign to user, complete, change description). In the Task based example you focus on one usecase at a time (which can be a composite/process related usecase) and ensure that the user interface reflects the users mental model (has relevant information for the user to complete the task) and captures the users intent.s
  • #45 Aggregates reference each other by Id only in a scalable system An Aggregate Root is more likely to match a Use Case boundary than any model structure
  • #69 Who informs the customer? What happens if Billing fails or times out?
  • #85 This minimizes the number of events that Service X (in this example) needs to understand and know. Here we trade data coupling for functional coupling
  • #86 This minimizes the number of events that Service X needs to understand and know. Here we trade data coupling for functional coupling It’s a choice between Loose Coupling and High Cohesion. If Service X needs to understand too MANY Events from other services, then his cohesion becomes lower and thereby the coupling gets higher
  • #103 Our read model is structurally oriented (the users perspective on the data in the given context) The write model is behavioural oriented (transaction and consistency boundary oriented)
  • #104 From http://www.codeagle.com/blog/2010/03/10/275/
  • #113 EventStore: Why store only the current state of the system. Mature industries store transactions and calculate the current state
  • #132 /Users/jeppe/git/finnova/demo-app/tellerapp