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