28
Scaling modern JVM applications with Akka toolkit Bojan Babić

Scaling modern JVM applications with Akka toolkit

Embed Size (px)

Citation preview

Scaling modern JVM applications with Akka toolkit

Bojan Babić

About me

• Full stack developer, focused on JVM

• Currently focused on scaling in messaging

@tenzki github.com/tenzki

Basic

• Unit of computation

• Motivated by relatable concepts

• Application as human organization

Mailbox

Actor

Isolated State

21 3 54 6

S1

S0

S2

Supervisor Actor 0

Supervisor Actor 1

Subordinate Actors

Akka

• Toolset with actor model as its foundation

• Written in Scala

• JVM with Java and Scala API

Example

• User in organization

• Described by name

• Can read and change it

// messages case object GetName case class SetName(name: String)

// actor class User(var name: String) extends Actor {

override def receive: Receive = { case GetName => sender() ! name case SetName(newName: String) => name = newName }

}

Plain actor

// messages case object GetName case class SetName(name: String)

// actor class User(var name: String) extends Actor {

override def receive: Receive = { case GetName => sender() ! name case SetName(newName: String) => name = newName }

}

Plain actor

// messages case object GetName case class SetName(name: String)

// actor class User(var name: String) extends Actor {

override def receive: Receive = { case GetName => sender() ! name case SetName(newName: String) => name = newName }

}

Plain actor

Persisting state

• Event sourcing

• Supported with persistence module

• Cassandra, with support for relational databases, mongo…

Persistent actor

// commands case object GetName case class SetName(name: String)

// events case class NameSet(name: String)

class User(id: UUID) extends PersistentActor {

var name: String = null

override def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

override def receiveCommand: Receive = { case GetName => sender() ! name case SetName(newName: String) => persist(NameSet(newName))(e => name = e.name) }

override def persistenceId: String = s"user:${id.toString}" }

// commands case object GetName case class SetName(name: String)

// events case class NameSet(name: String)

class User(id: UUID) extends PersistentActor {

var name: String = null

override def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

override def receiveCommand: Receive = { case GetName => sender() ! name case SetName(newName: String) => persist(NameSet(newName))(e => name = e.name) }

override def persistenceId: String = s"user:${id.toString}" }

Persistent actor

// commands case object GetName case class SetName(name: String)

// events case class NameSet(name: String)

class User(id: UUID) extends PersistentActor {

var name: String = null

override def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

override def receiveCommand: Receive = { case GetName => sender() ! name case SetName(newName: String) => persist(NameSet(newName))(e => name = e.name) }

override def persistenceId: String = s"user:${id.toString}" }

Persistent actor

// commands case object GetName case class SetName(name: String)

// events case class NameSet(name: String)

class User(id: UUID) extends PersistentActor {

var name: String = null

override def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

override def receiveCommand: Receive = { case GetName => sender() ! name case SetName(newName: String) => persist(NameSet(newName))(e => name = e.name) }

override def persistenceId: String = s"user:${id.toString}" }

Persistent actor

// commands case object GetName case class SetName(name: String)

// events case class NameSet(name: String)

class User(id: UUID) extends PersistentActor {

var name: String = null

override def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

override def receiveCommand: Receive = { case GetName => sender() ! name case SetName(newName: String) => persist(NameSet(newName))(e => name = e.name) }

override def persistenceId: String = s"user:${id.toString}" }

Persistent actor

// commands case object GetName case class SetName(name: String)

// events case class NameSet(name: String)

class User(id: UUID) extends PersistentActor {

var name: String = null

override def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

override def receiveCommand: Receive = { case GetName => sender() ! name case SetName(newName: String) => persist(NameSet(newName))(e => name = e.name) }

override def persistenceId: String = s"user:${id.toString}" }

Persistent actor

Running on multiple machines

• Cluster module

• Gossip protocol

• Cluster sharding

3

21

USERS

A B C Shards

User Actors

Cluster

Sharded actor

// commands trait UserMsg {val id: UUID} case class SetName(id: UUID, name: String) extends UserMsg case class GetName(id: UUID) extends UserMsg

// events case class NameSet(name: String)

class User extends PersistentActor {

var name: String = null

def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

def receiveCommand: Receive = { case GetName(_) => sender() ! name case SetName(_, newName: String) => persist(NameSet(newName))(e => name = e.name) }

def persistenceId: String = s"user:persistence:${self.path.name}" }

Sharded actor

// commands trait UserMsg {val id: UUID} case class SetName(id: UUID, name: String) extends UserMsg case class GetName(id: UUID) extends UserMsg

// events case class NameSet(name: String)

class User extends PersistentActor {

var name: String = null

def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

def receiveCommand: Receive = { case GetName(_) => sender() ! name case SetName(_, newName: String) => persist(NameSet(newName))(e => name = e.name) }

def persistenceId: String = s"user:persistence:${self.path.name}" }

Sharded actor

// commands trait UserMsg {val id: UUID} case class SetName(id: UUID, name: String) extends UserMsg case class GetName(id: UUID) extends UserMsg

// events case class NameSet(name: String)

class User extends PersistentActor {

var name: String = null

def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

def receiveCommand: Receive = { case GetName(_) => sender() ! name case SetName(_, newName: String) => persist(NameSet(newName))(e => name = e.name) }

def persistenceId: String = s"user:persistence:${self.path.name}" }

Sharded actor

object User { val NAME = "user"

val extractEntityId: ShardRegion.ExtractEntityId = { case command: UserMsg => (command.id.toString, command) }

val numberOfShards = 100

val extractShardId: ShardRegion.ExtractShardId = { case command: UserMsg => (command.id.toString.hashCode % numberOfShards).toString }

}

Optimize for scaling

• Command query responsibility segregation

• Different databases, eventually consistent

trait UserMsg {val id: UUID} // commands case class SetName(id: UUID, name: String) extends UserMsg

// events case class NameSet(name: String)

class UserProcessor extends PersistentActor {

var name: String = null

override def receiveRecover: Receive = { case NameSet(changeName: String) => name = changeName }

def receiveCommand: Receive = { case SetName(_, newName: String) => persist(NameSet(newName))(e => name = e.name) }

def persistenceId: String = s"user:cqrs:${self.path.name}"

}

CQRS actors

CQRS actors

// query case class GetName(id: UUID) extends UserMsg

class UserView extends Actor with Stash {

var name: String = null

def receive: Receive = { case GetName(id: UUID) => stash() case EventEnvelope(_, _, _, NameSet(newName: String)) => name = newName unstashAll() context.become(active) sender() ! Done }

def active: Receive = { case GetName(_) => sender() ! name case EventEnvelope(_, _, _, NameSet(newName: String)) => name = newName sender() ! Done }

}

Linking write and read

• Persistence query, API for streaming events

• Akka streams and reactive streams

• Streaming events from journal to any read database

Thanks!