scaling modern jvm applications with akka toolkit
TRANSCRIPT
About me
• Full stack developer, focused on JVM
• Currently focused on scaling in messaging
@tenzki github.com/tenzki
// 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
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