CQRS and Event Sourcing with Akka, Cassandra and RabbitMQ

29
1 CQRS & ES With Akka, Cassandra and RabbitMQ Miel Donkers, codecentric

Transcript of CQRS and Event Sourcing with Akka, Cassandra and RabbitMQ

1

CQRS & ESWith Akka, Cassandra and

RabbitMQMiel Donkers, codecentric

2 . 1

Why??

2 . 2

What is CQRS

2 . 3

What is CQRSCommand Query Responsibility Segregation

2 . 4

Traditional Architecture

© Martin Fowler

2 . 5

CQRS Architecture

© Martin Fowler

2 . 6

Storing dataSimple approach

Single databaseNormalized tables for write-side dataViews for denormalizing dataKeep queries simple

2 . 7

CQRS related to DDDDDD - Domain Driven DesignBounded ContextAggregateCommandEvent

2 . 8

What is ES

2 . 9

What is ESEvent SourcingEvent describes the state change of aggregateEvent is saved in event storeEvents can be replayed to re-create the current state.

2 . 10

CQRS & ES Combined

2 . 11

Architecture with Transactions

© Microsoft - CQRS Journey

2 . 12

Architecture with Events

© Microsoft - CQRS Journey

2 . 13

Architecture with Messages

© Microsoft - CQRS Journey

2 . 14

The hard parts

2 . 15

ConsistencyKey feature for the Event StoreWithout transactions, much work needed to make asreliableVersioning / timestampsEventual consistency

2 . 16

Error HandlingDistinguish between business faults and errors.

Errors can be re-tried.Business faults should have pre-determinedreaction.

2 . 17

Other disadvantagesMany moving parts, makes it hard to debug thesystem as a wholeCQRS not for every bounded context

2 . 18

Advantages

2 . 19

AdvantagesTask-based UIRead and write side can be optimized separatelyDebug in local environmentSmall components which are easy to update / fix

2 . 20

Code

2 . 21

User AggregateNot Finished �

2 . 22

User Repositoryprivate var users = Set.empty[User]

override def receiveCommand: Receive = { case GetUsers => sender() ! users case AddUser(name) if users.exists(_.name == name) => sender() ! UserExists(name) case AddUser(user) => persist(user) { persistedUser => receiveRecover(persistedUser) sender() ! UserAdded(persistedUser) } }

override def receiveRecover: Receive = { case user: User => users += user }

2 . 23

Event Senderprivate var unconfirmed = immutable.SortedMap.empty[Long, ActorPath]

override def receive: Receive = { case Msg(deliveryId, user) => unconfirmed = unconfirmed.updated(deliveryId, sender().path) val headersMap = Map(RabbitMQConstants.MESSAGE_ID -> deliveryId, RabbitMQConstants.CORRELATIONID -> deliveryId) camelSender ! CamelMessage(user.asJson.noSpaces, headersMap)

case CamelMessage(_, headers) => val deliveryId: Long = headers.getOrElse(RabbitMQConstants.MESSAGE_ID, - unconfirmed.get(deliveryId).foreach( senderActor => { unconfirmed -= deliveryId context.actorSelection(senderActor) ! Confirm(deliveryId) })

case Status.Failure(ex) =>

2 . 24

Event Receiveroverride def receive: Receive = { case msg: CamelMessage => val body: Xor[Error, User] = decode[User](msg.bodyAs[String]) body.fold({ error => sender() ! Failure(error) }, { user => sender() ! Ack }) case _ => log.warning("Unexpected event received") }

3 . 1

Wrapping Up

3 . 2

Feedback

3 . 3

The End 

Copyright 2016