Implementing CQRS with Akka
Michał Tomański
@michaltomanski
CQRS
What is this about?
- Command Query Responsibility Segregation
- Design/architecture pattern
class UserService {
public void createUser(User user);
public User getUser(long id);
}
class UserWriteService {
public void createUser(User user);
}
class UserReadService {
public User getUser(long id);
}
And that's basically it
Let's mix it!
Event Sourcing
- The state of an object is equivalent to all the past events applied one by one
- Persisting all the event
- Events are immutable, they are facts
- Events can be replayed to restore the state
DDD
- Domain-Driven Design
- Software development approach
- Domain first
- Can be used with many other things
...like ketchup
Event Sourcing
- System receives command
- Command gets validated
- Command generates events
- Events get persisted
- Events are applied on the state
DeactivateUser(id = 1)
Is the user with id=1 active?
UserDeactivated(id = 1)
UserDeactivated(id = 1) goes to DB
isActive = false
With DDD
Why?
- Persist facts
- Write only, never modify/delete
- Better domain modelling
- The state can be recovered
- No logic evolution problem
- Temporal queries
- Free audit logs / BI data
Event Sourcing
Challenges
- Change of mindset in data modelling
- Too many events to reply => snapshotting
- Events evolution, supporting old events
- Long startup time (events are being replayed) => do we need all the entities at the startup?
- High memory usage => sharding and passivation
- Cool, but how do we query this thing?
- Well, you don't
Why bother?
- Everything said earlier
- Suitable database engines for each side
- Independent read and write side scalability
- Independent data replication factor
- Read or write oriented domain
- Better performance with prepared views
- Loose coupling
Let's build something!
Awesome Timer
®
- Measures the time
- Online
- Users see other users' results (rivalry)
Overview
Awesome Timer
®
Write side
- Accept user's new time
- Remove the user's time (in case of unintentionally stopping the timer)
- Add +2s (penalty)
Awesome Timer
®
Read side
Show to the user his:
- Best single time
- Best average of 5
- Best average of 12
- Best average of 100
- Current average of 5, 12, 100
- Last 100 times
Show to the user other people's:
- Best single time
- Best average of 5
- Best average of 12
- Best average of 100
- Current average of 5, 12, 100
Awesome Timer
®
Writes and reads
- Extremely simple write side (just persist the username and the time), but with (hopefully) a lot of traffic
- Quite complicated and expensive reads (calculating best average of 100 among thousands of entries, querying multiple users)
- Typical CRUD is not the case here
Give me the code!
- But wait, Scala? Isn't it ugly?
def tailrecM[A, B](f: A => L \/ (A \/ B))(a: A): L \/ B =
f(a) match {
case l @ -\/(_) => l
case \/-(-\/(a0)) => tailrecM(f)(a0)
case \/-(rb @ \/-(_)) => rb
}
def ++[B1 >: B](xs: GenTraversableOnce[(A, B1)]): Map[A, B1] =
((repr: Map[A, B1]) /: xs.seq) (_ + _ )
- But wait, Scala? Isn't it ugly?
- Doesn't have to be
lazy val fib: Stream[Int] = 0 #:: 1 #:: fib.zip(fib.tail).map { case (a, b) => a + b }
fib drop 5 take 10 toList
// List(5, 8, 13, 21, 34, 55, 89, 144, 233, 377)
workers.foreach(_ ! PoisonPill)
case class Person(name: String, age: Int)
val somebody = Person("Michal", 25)
somebody match {
case Person(name, 25) => s"$name is 25 years old"
case Person(name, ) => s"$name is not 25 years old"
}
val reversedEven = 1 to 10 filter (_ % 2 == 0) reverse
val first = reversedEven.headOption.getOrElse(0)
- Ok, how do we do that? We build all of those persistence, projections etc?
- Let me introduce you Akka.
- A toolkit focused on actors model
- Useful for concurrent, distributed and message driven systems
- High-level abstraction
- For Scala and Java
Actor
- Encapsulate state and behavior
- React to a message
- Location transparency
- Supervision
Actor
class CounterActor extends Actor {
var counter = 0
def receive = {
case IncreaseCounter(amount: Int) => counter = counter + amount
case GimmeCounter => sender ! counter
case _ => println("Unknown message")
}
}
actor ! IncreaseCounter(5) // tell
actor ? GimmeCounter // ask
Persistent Actor
- Persist every valid event to the journal
- Do your business logic after persistence
- Replay all the events when needed
- To make the recovery faster, use snapshots
Persistent Actor
class PersistedCounter extends PersistentActor {
var counter = 0
override def receiveCommand: Receive = {
case increase: IncreaseCounter =>
persist(increase) { event =>
counter = counter + increase.amount
}
case GimmeCounter => sender ! counter
}
override def receiveRecover: Receive = {
case increase: IncreaseCounter => counter = counter + increase.amount
case RecoveryCompleted => println("Events recovery completed")
}
override def persistenceId: String = "persisted-counter"
}
Persistent Actor
As a domain entity
Persistent Actor
Journal (Cassandra)
Cluster sharding
As a domain entities sharding
Projection
Akka Persistence Query as a projection
Akka persistence query
Postgresql
Controllers
Communicating with the world
Eventual consistency
Should I care?
- Read data will be consistent with what is inside the event store... eventually.
- Getting an updated view by the user might take a while
- But isn't it always the case?
- Data is not wrong, it's just old
Eventual consistency
Should I care?
- Well, there might be a problem while making domain validations against views...
- When? When not?
- Send a compensation event
- Tweak with polling times
- Keep track of inconsistency issues
- ... Or do not design your (micro)service this way
¯\_(ツ)_/¯
Cubing time
Live demo
Summary
We have a service...
- That can have read and write model scaled independently
- With databases suitable for read and write
- Easy to integrate with other services via events messaging
- Easy to be scaled horizontally with state recovered
- With complete and immutable events history
- With prepared expensive views
- Easy to be tested and debugged (just dump events from prod)
- Written using DDD
- In Scala (•‿•)
Implementing CQRS with Akka
Michał Tomański
@michaltomanski
slides.com/michaltomanski/cqrs
github.com/michaltomanski/cqrs-demo
CQRS Kielce
By Michał Tomański
CQRS Kielce
- 1,486