SlideShare a Scribd company logo
Event-Sourced µServices with Play!
and Akka
by Dominik Dorn @ Scala Vienna 2018-05
Dominik Dorn
4 Freelance Software Engineer
4 Leader of
4 Java-Vienna Meetup
4 PlayFramework.Wien
Meetup
4 PlayFramework Integrator
#Java, #Scala, #Akka, #Spark
#Play, #Cassandra, #Kafka,#Postgres
2
Overview
4 Intro to Event Sourcing
4 (Short!) Intro to Actors + Actors in Akka
4 Event-Sourcing with Akka-Persistence
4 Intro to CQRS
4 Clustering with Akka
4 Code-Base/Dev-Env structuring + tips
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 3
Intro to Event-Sourcing
4 Reasons + Example
4 Overview
4 Commands + Events
4 The Journal
4 Command-Handler + Event-Handler
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 4
Reasons for Event Sourcing
4 High Performance
4 Immutable-Events
4 Append-Only Journal
4 Increased write-performance
4 No Object-Relation-Mapping needed
4 Full-Audit trail (e.g. GDPR) - debugging
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 5
Examples of Event-Sourcing
4 Your Bank-/Paypal-Account
4 Git
4 Order Tracking (Post, DPD, UPS, etc.)
4 kind of: Blockchain .. BitCoin anyone?
4 PostgreSQL/Oracle/etc.: Transaction Log!
4 but without meaning!
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 6
High-Performance event-sourced clustered Microservices with Play! & Akka @ Scala-Vienna May 2018
Commands & Events
4 Commands - Something the users wants us to do
case class RegisterUser(email: String, name: String)
case class ChangeName(email: String, newName: String)
4 Events - Something that happened in the past
case class UserRegistered(email: String, name: String)
case class NameChanged(newName: String)
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 8
The (Event-)Journal
4 Single Source of Truth
4 Stores the Events
4 Groups Events by Entity-ID
4 Sequence-Number for every Entity-ID
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 9
The (Event-)Journal
looks like this
Entity-ID Seq# Type Payload (e.g. in JSON)
user-1 1 UserRegistered {email:"dominik.dorn@gmail.com",
name:"Dominikk Dorn"}
user-1 2 NameChanged {newName:"Dominik Dorn"}
user-2 3 UserRegistered {email:"dominik@dominikdorn.com",
name:"Dominik Dorn"}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 10
Command-Handler
4 Enforces constraints
var registeredEmails: Vector[String] = Vector.empty
def receiveCommand = {
case RegisterUser(email, name) =>
if(registeredEmails.exists(_ == email)) {
throw new UserAlreadyExistsException();
} else {
// registering..
}
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 11
Command-Handler
4 Persists to the Journal, before executing
case RegisterUser(email, name) =>
if(!registeredEmails.exists(_ == email)) {
persistToJournal(UserRegistered(email, name)){ ev =>
sendWelcomeMail(email, name)
}
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 12
Command-Handler
4 (Can) Delegate to the event-handler(s)
case RegisterUser(email, name) =>
...
persistToJournal(UserRegistered(email, name)){ ev =>
sendWelcomeMail(email, name)
eventHandlers.foreach(handler => handler.receiveEvent(ev))
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 13
Event-Handlers
4 Work on Events from the past
var registeredEmails: Vector[String] = Vector.empty
def receiveEvent = {
case UserRegistered(email, name) =>
registeredEmails = registeredEmails :+ email
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 14
(Short!) Intro to Actors (in Akka)
Actors Humans
have state (fields, lists, etc.) have properties (height, weight,
knowledge)
communicate via messages communicate via voice, sms,
letters, looks, touch, ...
live in an ActorSystem live on Planet Earth
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 15
Creation
Actors Humans
system = new ActorSystem() god.create(Planet("Earth"))
system.actorOf(Props(new
Female("Eve")))
god.create(Female("Eve"))
context.childOf(Props(new
Male())
eve.fork() // +adam
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 16
Getting information
about the current state of the soccer game
Actors Humans
gameActor.tell(GetCurrentScore) shout("Hey Steve, what's the
score?")
sender().tell(Score(3,2)) shout("Its 3 to 2 for Manchester!")
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 17
Actors in Akka
class Human extends akka.actor.Actor {
var name = ""
override def receive = {
case GiveName(name: String) =>
name = name;
sender() ! Hi(s"I'm $name")
}
}
val adam: ActorRef = actorSystem.actorOf(Props(new Human()))
adam ! GiveName("Adam")
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 18
Persistent-Actors
High-Performance event-sourced clustered Microservices with Play! & Akka @ Scala-Vienna May 2018
Persistent-Actor Managers
class UserManager(@Named("email") email: ActorRef)
extends PersistentActor {
val persistenceId: String = s"userManager"
def getOrCreateChild(id:String): ActorRef = context.child(id).
getOrElse(context.actorOf(User.props(id), id, email))
def receiveCommand = { ... } // handle commands
def receiveRecover = { ... } // can be empty
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 21
Persistent-Actors
class User(id: String, email: ActorRef)
extends PersistentActor {
// entity-id in the journal
val persistenceId: String = s"user-$id"
def receiveCommand = { ... } // handle commands
def receiveRecover = { ... } // handle events
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 22
Command-Handler
def receiveCommand = {
case RegisterUser(email, name) =>
// email constraints already checked before
persist(UserRegistered(email, name)) { ev =>
// ^--- persist stores event into the journal
receiveRecover.apply(ev)
// ^--- apply the event to the event handler
email ! Email.Commands.SendWelcomeMail(email, name)
// tell that we're done
sender() ! UserRegisteredAnswer(email, name)
}
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 23
Command-Handler
def receiveCommand = {
...
case ChangeName(email, name) =>
if(name != this.name) {
persist(NameChanged(email, name)) { ev =>
receiveRecover.apply(ev)
}
}
sender() ! akka.Done // the new name is set
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 24
Event-Handler
var email = ""
var name = ""
def receiveRecover = {
case UserRegistered(email, name) =>
this.email = email
this.name = name
case NameChanged(email, newName) =>
this.name = newName
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 25
Many events? Take Snapshots!
var lastSequenceNr = 0 // gets updated in event handler
def maybeMakeSnapshot() = if(lastSequenceNr % 1000 == 0) {
saveSnapshot((email, name))
}
def receiveCommand = { ....
persist(NameChanged(email, name)) { ev =>
receiveRecover.apply(ev)
maybeMakeSnapshot()
}
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 26
And Recover from it!
def receiveRecover = {
case UserRegistered(email, name) => ...
case NameChanged(email, newName) => ...
case SnapshotOffer(metaData, snapshot: (String, String)) =>
this.email = snapshot._1
this.name = snapshot._2
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 27
Keep memory low, set a ReceiveTimeout
class SomeActor extends Actor {
context.setReceiveTimeout(3 seconds)
def receive = {
// ....
case ReceiveTimeout => context.stop(self)
}
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 28
Best Practices: Protocols
object User {
object Events {
sealed trait Event
...
}
object Commands {
sealed trait Command
...
}
object Queries {
sealed trait Query
...
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 29
Best Practices: Protocols - Events
object User {
object Events {
sealed trait Event
case class UserCreatedEvent(email: String, name: String) extends Event
case class NameChangedEvent(newName: String) extends Event
case class FriendAddedEvent(friendEmail: String) extends Event
}
...
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 30
gives you: IDE support!
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 31
gives you: Compile-time safety!
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 32
Protocols: Commands + Responses
object User { ...
object Commands {
sealed trait Command
case class CreateUserCommand(email: String, name: String) extends Command
sealed trait CreateUserResponse
case class UserCreatedResponse(email: String) extends CreateUserResponse
case object UserCreationFailedResponse extends CreateUserResponse
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 33
Protocols: Queries + Responses
object User { ...
object Queries {
sealed trait Query
sealed trait GetUserDetailsResponse
case class GetUserDetails(email: String) extends Query
case class UserDetails(email: String, name: String)
extends GetUserDetailsResponse
sealed trait GetFriendsResponse
case class GetFriends(email: String) extends Query
case class Friends(friends: Seq[String]) extends GetFriendsResponse
case object UserNotFound extends GetUserDetailsResponse with GetFriendsResponse
Command Query Responsibility
Segregation (CQRS)
4 Split Actions (Commands) and Questions (Queries)
4 Divide the code-base according to these separations
4 Create optimized datastores (read-sides) for the
various queries
4 If necessary, create+scale read- + write-µServices
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 35
CQRS-Example: Get the names of all
Users
different approaches
4 The (naive) Actor way: Ask everyone!
4 Read the Event-Log
4 Create custom read-sides
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 36
Ask! Everyone
class GetAllNamesActor( ids: Vector[String], aggregate: ActorRef,
answerTo: ActorRef) extends Actor {
var repliesWaiting: Vector[String] = ids
var data: Map[String, String] = Map.empty
ids.foreach(id => aggregate ! GetUserData(id))
def receive = {
case UserData(id, name) =>
repliesWaiting = repliesWaiting.filterNot(_ == id)
data = data.updated(id, name)
if(repliesWaiting.isEmpty){
answerTo ! GetAllNamesResponse(data.values)
}
}
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 37
Using Akka-Persistence-Query
class GetAllNamesActor(...,answerTo: ActorRef,...) {
val readJournal = PersistenceQuery(context.system)
.readJournalFor[CassandraReadJournal](CassandraReadJournal.Identifier)
val importFuture: Future[Done] = startInitialImport(self).map(_ => Done)
pipe(importFuture).to(self)
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 38
Receive Events via Akka-Streams
val ALL_CURRENT_IDS = readJournal.currentPersistenceIds()
val ALL_IDS_IN_FUTURE = readJournal.persistenceIds()
val FILTER_USERS = Flow[String].filter(s => s.startsWith("user-"))
def startInitialImport(recipient: ActorRef) =
ALL_CURRENT_IDS.via(FILTER_USERS) // get all user persistence-ids
.flatMapConcat(id => readJournal
/* ^ */ .currentEventsByPersistenceId(id, 0, Integer.MAX_VALUE))
// |--- join with all the events of these users
.runForeach(ev => recipient ! ev)
// ^ send all the events to recipient / ourself
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 39
Process the events in the actor
class InMemoryGetAllNamesActor(...,answerTo: ActorRef,...) {
...
def receive = {
case Done => answerTo ! GetAllNamesResponse(data.values)
case EventEnvelope(_, _, _, ev) => ev match {
case UserRegistered(email, name) => ... // fill state
case NameChanged(email, newName) => ... // fill state
}
}
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 40
Create a SQL read-side
class UserSQLReadSideEventHandler() extends Actor {
// ... like in GetAllNamesActor with Akka-Streams
def receive = {
case EventEnvelope(_, _, _, ev) => ev match {
case UserRegistered(email, name) =>
SQL(s"INSERT INTO users VALUES (...)")
case NameChanged(email, newName) =>
SQL(s"UPDATE users SET name = ${newName} where email = ${email}")
}
}
}
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 41
Akka-Streams Performance/low latency
Optimizations
4 Tag your Events using a WriteEventAdapter, then
read with readJournal.eventsByTag()
4 Publish Events in the CommandHandler to the Event-
Stream, subscribe in the EventHandler(s)
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 42
Clustering with Akka
4 Use the akka-cluster module
4 Use cluster sharding to place aggregates on nodes
4 Don't distribute the contents of aggregates!
4 Use cluster singletons for Read-Side Event-Handlers
4 #Shardregions: ~ 10 x #cluster-nodes
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 43
Clustering with Akka
High-Performance event-sourced clustered Microservices with Play! & Akka @ Scala-Vienna May 2018
Creating a Shard-Region
val userRegion: ActorRef = ClusterSharding(system).start (
typeName = "User",
entityProps = Props[User],
settings = ClusterShardingSettings(system),
// method how to extract the entity id of a message
extractEntityId = extractEntityId,
// how to derive the correct shard of a entity
extractShardId = extractShardId
)
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 46
PersistenceId + Forwarding 1
class UserManager extends PersistentActor {
val id = extractIdFrom(self.path.name)
// extract ID from path generated by ClusterSharding
override def persistenceId = s"user-$id"
val userRegion = ClusterSharding(system).start (...)
def receive = {
// forward messages to the correct actors
case x : ChangeUserName => userRegion.forward(x)
// ...
}
1 
More Details in "Mastering Akka, 1st Edition, 2016, Pakt Publishing" (Ebook currently 10 USD!)
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 47
Efficient µServices-
Code-Base
structuring
4 project (+docker image) per
bounded-context
4 e.g. authentication, user-
management, billing
4 Root-Project with
VirtualHostRequestHandler
Virtual-Host Request-Handler2
class RequestHandler @Inject()(.... authRouter: auth.Routes,
billingRouter: billing.Routes, petClinicRouter: petclinic.Routes
) extends DefaultHttpRequestHandler(Router.empty, ...) {
override def routeRequest(request: RequestHeader): Option[Handler] = {
request.host.split('.').headOption match {
case Some("auth") => authRouter.routes.lift(request)
case Some("billing") => billingRouter.routes.lift(request)
case Some("petclinic") => petClinicRouter.routes.lift(request)
case _ => super.routeRequest(request)
}
2 
see https://www.playframework.com/documentation/2.6.x/ScalaHttpRequestHandlers
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 49
Code Structure
4 package per Entity
4 package per use-case
4 Input- and Output-Datatypes
4 Tailored for every use-case!
50
High-Performance event-sourced clustered Microservices with Play! & Akka @ Scala-Vienna May 2018
THANK YOU!
Blog https://dominikdorn.com
Twitter @domdorn
Xing https://www.xing.com/profile/Dominik_Dorn
LinkedIn https://www.linkedin.com/in/dominik-dorn
Java-Vienna Meetup | PlayFramework.Wien Meetup
#Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 52

More Related Content

PDF
High-Performance event-sourced clustered Microservices with Play! & Akka @ Vo...
PDF
20180321 使用aws 建立 notification system
PDF
Nils Mohr & Jake Pearce - 100 years of flight data at British Airways. Past, ...
PDF
A Crash Course on Serverless Applications in Python
PDF
Vaadin 10 on PlayFramework
PDF
[DataCon.TW 2018] Metadata Store: Generalized Entity Database for Intelligenc...
PDF
2014-11-26 | Creating a BitTorrent Client with Scala and Akka, Part 1 (Vienna...
PDF
Norikra: SQL Stream Processing In Ruby
High-Performance event-sourced clustered Microservices with Play! & Akka @ Vo...
20180321 使用aws 建立 notification system
Nils Mohr & Jake Pearce - 100 years of flight data at British Airways. Past, ...
A Crash Course on Serverless Applications in Python
Vaadin 10 on PlayFramework
[DataCon.TW 2018] Metadata Store: Generalized Entity Database for Intelligenc...
2014-11-26 | Creating a BitTorrent Client with Scala and Akka, Part 1 (Vienna...
Norikra: SQL Stream Processing In Ruby

Similar to High-Performance event-sourced clustered Microservices with Play! & Akka @ Scala-Vienna May 2018 (20)

PDF
Java-Vienna: Spring Boot 1.x Overview/Intro by Dominik Dorn
PDF
Event Sourcing with Kotlin, who needs frameworks!
PDF
Mashing Up The Guardian
PDF
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
PDF
Interactive Content Authoring for A153 ATSC Mobile Digital Television Employi...
PDF
Let's play a game with blackfire player
PPT
Learning with F#
PPTX
Keeping Spark on Track: Productionizing Spark for ETL
PDF
Best practices for crafting high quality PHP apps (Bulgaria 2019)
PDF
PDF
Shaping Clouds with Terraform
PDF
[DoKDayNA2022] - Architecting Your First Event Driven Serverless Streaming Ap...
PDF
Crafting Quality PHP Applications (ConFoo YVR 2017)
PDF
Javantura v3 - ELK – Big Data for DevOps – Maarten Mulders
PDF
Monitoring with Syslog and EventMachine (RailswayConf 2012)
PDF
Oliver hookins puppetcamp2011
PPTX
F# Type Providers in Depth
PDF
Crafting Quality PHP Applications (PHP Benelux 2018)
Java-Vienna: Spring Boot 1.x Overview/Intro by Dominik Dorn
Event Sourcing with Kotlin, who needs frameworks!
Mashing Up The Guardian
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
Interactive Content Authoring for A153 ATSC Mobile Digital Television Employi...
Let's play a game with blackfire player
Learning with F#
Keeping Spark on Track: Productionizing Spark for ETL
Best practices for crafting high quality PHP apps (Bulgaria 2019)
Shaping Clouds with Terraform
[DoKDayNA2022] - Architecting Your First Event Driven Serverless Streaming Ap...
Crafting Quality PHP Applications (ConFoo YVR 2017)
Javantura v3 - ELK – Big Data for DevOps – Maarten Mulders
Monitoring with Syslog and EventMachine (RailswayConf 2012)
Oliver hookins puppetcamp2011
F# Type Providers in Depth
Crafting Quality PHP Applications (PHP Benelux 2018)
Ad

Recently uploaded (20)

PDF
Supply Chain Operations Speaking Notes -ICLT Program
PDF
RMMM.pdf make it easy to upload and study
PDF
Microbial disease of the cardiovascular and lymphatic systems
PDF
O5-L3 Freight Transport Ops (International) V1.pdf
PPTX
school management -TNTEU- B.Ed., Semester II Unit 1.pptx
PDF
TR - Agricultural Crops Production NC III.pdf
PPTX
Cell Types and Its function , kingdom of life
PDF
3rd Neelam Sanjeevareddy Memorial Lecture.pdf
PDF
O7-L3 Supply Chain Operations - ICLT Program
PPTX
Introduction_to_Human_Anatomy_and_Physiology_for_B.Pharm.pptx
PDF
grade 11-chemistry_fetena_net_5883.pdf teacher guide for all student
PPTX
Week 4 Term 3 Study Techniques revisited.pptx
PPTX
PPT- ENG7_QUARTER1_LESSON1_WEEK1. IMAGERY -DESCRIPTIONS pptx.pptx
PDF
Abdominal Access Techniques with Prof. Dr. R K Mishra
PDF
Classroom Observation Tools for Teachers
PDF
Complications of Minimal Access Surgery at WLH
PPTX
Renaissance Architecture: A Journey from Faith to Humanism
PPTX
human mycosis Human fungal infections are called human mycosis..pptx
PDF
VCE English Exam - Section C Student Revision Booklet
PDF
Origin of periodic table-Mendeleev’s Periodic-Modern Periodic table
Supply Chain Operations Speaking Notes -ICLT Program
RMMM.pdf make it easy to upload and study
Microbial disease of the cardiovascular and lymphatic systems
O5-L3 Freight Transport Ops (International) V1.pdf
school management -TNTEU- B.Ed., Semester II Unit 1.pptx
TR - Agricultural Crops Production NC III.pdf
Cell Types and Its function , kingdom of life
3rd Neelam Sanjeevareddy Memorial Lecture.pdf
O7-L3 Supply Chain Operations - ICLT Program
Introduction_to_Human_Anatomy_and_Physiology_for_B.Pharm.pptx
grade 11-chemistry_fetena_net_5883.pdf teacher guide for all student
Week 4 Term 3 Study Techniques revisited.pptx
PPT- ENG7_QUARTER1_LESSON1_WEEK1. IMAGERY -DESCRIPTIONS pptx.pptx
Abdominal Access Techniques with Prof. Dr. R K Mishra
Classroom Observation Tools for Teachers
Complications of Minimal Access Surgery at WLH
Renaissance Architecture: A Journey from Faith to Humanism
human mycosis Human fungal infections are called human mycosis..pptx
VCE English Exam - Section C Student Revision Booklet
Origin of periodic table-Mendeleev’s Periodic-Modern Periodic table
Ad

High-Performance event-sourced clustered Microservices with Play! & Akka @ Scala-Vienna May 2018

  • 1. Event-Sourced µServices with Play! and Akka by Dominik Dorn @ Scala Vienna 2018-05
  • 2. Dominik Dorn 4 Freelance Software Engineer 4 Leader of 4 Java-Vienna Meetup 4 PlayFramework.Wien Meetup 4 PlayFramework Integrator #Java, #Scala, #Akka, #Spark #Play, #Cassandra, #Kafka,#Postgres 2
  • 3. Overview 4 Intro to Event Sourcing 4 (Short!) Intro to Actors + Actors in Akka 4 Event-Sourcing with Akka-Persistence 4 Intro to CQRS 4 Clustering with Akka 4 Code-Base/Dev-Env structuring + tips #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 3
  • 4. Intro to Event-Sourcing 4 Reasons + Example 4 Overview 4 Commands + Events 4 The Journal 4 Command-Handler + Event-Handler #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 4
  • 5. Reasons for Event Sourcing 4 High Performance 4 Immutable-Events 4 Append-Only Journal 4 Increased write-performance 4 No Object-Relation-Mapping needed 4 Full-Audit trail (e.g. GDPR) - debugging #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 5
  • 6. Examples of Event-Sourcing 4 Your Bank-/Paypal-Account 4 Git 4 Order Tracking (Post, DPD, UPS, etc.) 4 kind of: Blockchain .. BitCoin anyone? 4 PostgreSQL/Oracle/etc.: Transaction Log! 4 but without meaning! #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 6
  • 8. Commands & Events 4 Commands - Something the users wants us to do case class RegisterUser(email: String, name: String) case class ChangeName(email: String, newName: String) 4 Events - Something that happened in the past case class UserRegistered(email: String, name: String) case class NameChanged(newName: String) #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 8
  • 9. The (Event-)Journal 4 Single Source of Truth 4 Stores the Events 4 Groups Events by Entity-ID 4 Sequence-Number for every Entity-ID #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 9
  • 10. The (Event-)Journal looks like this Entity-ID Seq# Type Payload (e.g. in JSON) user-1 1 UserRegistered {email:"dominik.dorn@gmail.com", name:"Dominikk Dorn"} user-1 2 NameChanged {newName:"Dominik Dorn"} user-2 3 UserRegistered {email:"dominik@dominikdorn.com", name:"Dominik Dorn"} #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 10
  • 11. Command-Handler 4 Enforces constraints var registeredEmails: Vector[String] = Vector.empty def receiveCommand = { case RegisterUser(email, name) => if(registeredEmails.exists(_ == email)) { throw new UserAlreadyExistsException(); } else { // registering.. } } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 11
  • 12. Command-Handler 4 Persists to the Journal, before executing case RegisterUser(email, name) => if(!registeredEmails.exists(_ == email)) { persistToJournal(UserRegistered(email, name)){ ev => sendWelcomeMail(email, name) } } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 12
  • 13. Command-Handler 4 (Can) Delegate to the event-handler(s) case RegisterUser(email, name) => ... persistToJournal(UserRegistered(email, name)){ ev => sendWelcomeMail(email, name) eventHandlers.foreach(handler => handler.receiveEvent(ev)) } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 13
  • 14. Event-Handlers 4 Work on Events from the past var registeredEmails: Vector[String] = Vector.empty def receiveEvent = { case UserRegistered(email, name) => registeredEmails = registeredEmails :+ email } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 14
  • 15. (Short!) Intro to Actors (in Akka) Actors Humans have state (fields, lists, etc.) have properties (height, weight, knowledge) communicate via messages communicate via voice, sms, letters, looks, touch, ... live in an ActorSystem live on Planet Earth #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 15
  • 16. Creation Actors Humans system = new ActorSystem() god.create(Planet("Earth")) system.actorOf(Props(new Female("Eve"))) god.create(Female("Eve")) context.childOf(Props(new Male()) eve.fork() // +adam #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 16
  • 17. Getting information about the current state of the soccer game Actors Humans gameActor.tell(GetCurrentScore) shout("Hey Steve, what's the score?") sender().tell(Score(3,2)) shout("Its 3 to 2 for Manchester!") #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 17
  • 18. Actors in Akka class Human extends akka.actor.Actor { var name = "" override def receive = { case GiveName(name: String) => name = name; sender() ! Hi(s"I'm $name") } } val adam: ActorRef = actorSystem.actorOf(Props(new Human())) adam ! GiveName("Adam") #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 18
  • 21. Persistent-Actor Managers class UserManager(@Named("email") email: ActorRef) extends PersistentActor { val persistenceId: String = s"userManager" def getOrCreateChild(id:String): ActorRef = context.child(id). getOrElse(context.actorOf(User.props(id), id, email)) def receiveCommand = { ... } // handle commands def receiveRecover = { ... } // can be empty } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 21
  • 22. Persistent-Actors class User(id: String, email: ActorRef) extends PersistentActor { // entity-id in the journal val persistenceId: String = s"user-$id" def receiveCommand = { ... } // handle commands def receiveRecover = { ... } // handle events } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 22
  • 23. Command-Handler def receiveCommand = { case RegisterUser(email, name) => // email constraints already checked before persist(UserRegistered(email, name)) { ev => // ^--- persist stores event into the journal receiveRecover.apply(ev) // ^--- apply the event to the event handler email ! Email.Commands.SendWelcomeMail(email, name) // tell that we're done sender() ! UserRegisteredAnswer(email, name) } } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 23
  • 24. Command-Handler def receiveCommand = { ... case ChangeName(email, name) => if(name != this.name) { persist(NameChanged(email, name)) { ev => receiveRecover.apply(ev) } } sender() ! akka.Done // the new name is set } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 24
  • 25. Event-Handler var email = "" var name = "" def receiveRecover = { case UserRegistered(email, name) => this.email = email this.name = name case NameChanged(email, newName) => this.name = newName } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 25
  • 26. Many events? Take Snapshots! var lastSequenceNr = 0 // gets updated in event handler def maybeMakeSnapshot() = if(lastSequenceNr % 1000 == 0) { saveSnapshot((email, name)) } def receiveCommand = { .... persist(NameChanged(email, name)) { ev => receiveRecover.apply(ev) maybeMakeSnapshot() } } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 26
  • 27. And Recover from it! def receiveRecover = { case UserRegistered(email, name) => ... case NameChanged(email, newName) => ... case SnapshotOffer(metaData, snapshot: (String, String)) => this.email = snapshot._1 this.name = snapshot._2 } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 27
  • 28. Keep memory low, set a ReceiveTimeout class SomeActor extends Actor { context.setReceiveTimeout(3 seconds) def receive = { // .... case ReceiveTimeout => context.stop(self) } } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 28
  • 29. Best Practices: Protocols object User { object Events { sealed trait Event ... } object Commands { sealed trait Command ... } object Queries { sealed trait Query ... } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 29
  • 30. Best Practices: Protocols - Events object User { object Events { sealed trait Event case class UserCreatedEvent(email: String, name: String) extends Event case class NameChangedEvent(newName: String) extends Event case class FriendAddedEvent(friendEmail: String) extends Event } ... #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 30
  • 31. gives you: IDE support! #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 31
  • 32. gives you: Compile-time safety! #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 32
  • 33. Protocols: Commands + Responses object User { ... object Commands { sealed trait Command case class CreateUserCommand(email: String, name: String) extends Command sealed trait CreateUserResponse case class UserCreatedResponse(email: String) extends CreateUserResponse case object UserCreationFailedResponse extends CreateUserResponse } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 33
  • 34. Protocols: Queries + Responses object User { ... object Queries { sealed trait Query sealed trait GetUserDetailsResponse case class GetUserDetails(email: String) extends Query case class UserDetails(email: String, name: String) extends GetUserDetailsResponse sealed trait GetFriendsResponse case class GetFriends(email: String) extends Query case class Friends(friends: Seq[String]) extends GetFriendsResponse case object UserNotFound extends GetUserDetailsResponse with GetFriendsResponse
  • 35. Command Query Responsibility Segregation (CQRS) 4 Split Actions (Commands) and Questions (Queries) 4 Divide the code-base according to these separations 4 Create optimized datastores (read-sides) for the various queries 4 If necessary, create+scale read- + write-µServices #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 35
  • 36. CQRS-Example: Get the names of all Users different approaches 4 The (naive) Actor way: Ask everyone! 4 Read the Event-Log 4 Create custom read-sides #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 36
  • 37. Ask! Everyone class GetAllNamesActor( ids: Vector[String], aggregate: ActorRef, answerTo: ActorRef) extends Actor { var repliesWaiting: Vector[String] = ids var data: Map[String, String] = Map.empty ids.foreach(id => aggregate ! GetUserData(id)) def receive = { case UserData(id, name) => repliesWaiting = repliesWaiting.filterNot(_ == id) data = data.updated(id, name) if(repliesWaiting.isEmpty){ answerTo ! GetAllNamesResponse(data.values) } } } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 37
  • 38. Using Akka-Persistence-Query class GetAllNamesActor(...,answerTo: ActorRef,...) { val readJournal = PersistenceQuery(context.system) .readJournalFor[CassandraReadJournal](CassandraReadJournal.Identifier) val importFuture: Future[Done] = startInitialImport(self).map(_ => Done) pipe(importFuture).to(self) } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 38
  • 39. Receive Events via Akka-Streams val ALL_CURRENT_IDS = readJournal.currentPersistenceIds() val ALL_IDS_IN_FUTURE = readJournal.persistenceIds() val FILTER_USERS = Flow[String].filter(s => s.startsWith("user-")) def startInitialImport(recipient: ActorRef) = ALL_CURRENT_IDS.via(FILTER_USERS) // get all user persistence-ids .flatMapConcat(id => readJournal /* ^ */ .currentEventsByPersistenceId(id, 0, Integer.MAX_VALUE)) // |--- join with all the events of these users .runForeach(ev => recipient ! ev) // ^ send all the events to recipient / ourself #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 39
  • 40. Process the events in the actor class InMemoryGetAllNamesActor(...,answerTo: ActorRef,...) { ... def receive = { case Done => answerTo ! GetAllNamesResponse(data.values) case EventEnvelope(_, _, _, ev) => ev match { case UserRegistered(email, name) => ... // fill state case NameChanged(email, newName) => ... // fill state } } } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 40
  • 41. Create a SQL read-side class UserSQLReadSideEventHandler() extends Actor { // ... like in GetAllNamesActor with Akka-Streams def receive = { case EventEnvelope(_, _, _, ev) => ev match { case UserRegistered(email, name) => SQL(s"INSERT INTO users VALUES (...)") case NameChanged(email, newName) => SQL(s"UPDATE users SET name = ${newName} where email = ${email}") } } } #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 41
  • 42. Akka-Streams Performance/low latency Optimizations 4 Tag your Events using a WriteEventAdapter, then read with readJournal.eventsByTag() 4 Publish Events in the CommandHandler to the Event- Stream, subscribe in the EventHandler(s) #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 42
  • 43. Clustering with Akka 4 Use the akka-cluster module 4 Use cluster sharding to place aggregates on nodes 4 Don't distribute the contents of aggregates! 4 Use cluster singletons for Read-Side Event-Handlers 4 #Shardregions: ~ 10 x #cluster-nodes #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 43
  • 46. Creating a Shard-Region val userRegion: ActorRef = ClusterSharding(system).start ( typeName = "User", entityProps = Props[User], settings = ClusterShardingSettings(system), // method how to extract the entity id of a message extractEntityId = extractEntityId, // how to derive the correct shard of a entity extractShardId = extractShardId ) #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 46
  • 47. PersistenceId + Forwarding 1 class UserManager extends PersistentActor { val id = extractIdFrom(self.path.name) // extract ID from path generated by ClusterSharding override def persistenceId = s"user-$id" val userRegion = ClusterSharding(system).start (...) def receive = { // forward messages to the correct actors case x : ChangeUserName => userRegion.forward(x) // ... } 1  More Details in "Mastering Akka, 1st Edition, 2016, Pakt Publishing" (Ebook currently 10 USD!) #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 47
  • 48. Efficient µServices- Code-Base structuring 4 project (+docker image) per bounded-context 4 e.g. authentication, user- management, billing 4 Root-Project with VirtualHostRequestHandler
  • 49. Virtual-Host Request-Handler2 class RequestHandler @Inject()(.... authRouter: auth.Routes, billingRouter: billing.Routes, petClinicRouter: petclinic.Routes ) extends DefaultHttpRequestHandler(Router.empty, ...) { override def routeRequest(request: RequestHeader): Option[Handler] = { request.host.split('.').headOption match { case Some("auth") => authRouter.routes.lift(request) case Some("billing") => billingRouter.routes.lift(request) case Some("petclinic") => petClinicRouter.routes.lift(request) case _ => super.routeRequest(request) } 2  see https://www.playframework.com/documentation/2.6.x/ScalaHttpRequestHandlers #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 49
  • 50. Code Structure 4 package per Entity 4 package per use-case 4 Input- and Output-Datatypes 4 Tailored for every use-case! 50
  • 52. THANK YOU! Blog https://dominikdorn.com Twitter @domdorn Xing https://www.xing.com/profile/Dominik_Dorn LinkedIn https://www.linkedin.com/in/dominik-dorn Java-Vienna Meetup | PlayFramework.Wien Meetup #Scala #Vienna 2018 May-Meetup | 4th June 2018 | Event Sourced µServices with Akka and Play! | Dominik Dorn @domdorn 52