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
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
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
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
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

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

Event Sourcing with Kotlin, who needs frameworks!
Event Sourcing with Kotlin, who needs frameworks!Event Sourcing with Kotlin, who needs frameworks!
Event Sourcing with Kotlin, who needs frameworks!
Nico Krijnen
 
Monitoring with Syslog and EventMachine (RailswayConf 2012)
Monitoring  with  Syslog and EventMachine (RailswayConf 2012)Monitoring  with  Syslog and EventMachine (RailswayConf 2012)
Monitoring with Syslog and EventMachine (RailswayConf 2012)
Wooga
 

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

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

Recently uploaded

The Roman Empire A Historical Colossus.pdf
The Roman Empire A Historical Colossus.pdfThe Roman Empire A Historical Colossus.pdf
The Roman Empire A Historical Colossus.pdf
kaushalkr1407
 

Recently uploaded (20)

Matatag-Curriculum and the 21st Century Skills Presentation.pptx
Matatag-Curriculum and the 21st Century Skills Presentation.pptxMatatag-Curriculum and the 21st Century Skills Presentation.pptx
Matatag-Curriculum and the 21st Century Skills Presentation.pptx
 
How to Split Bills in the Odoo 17 POS Module
How to Split Bills in the Odoo 17 POS ModuleHow to Split Bills in the Odoo 17 POS Module
How to Split Bills in the Odoo 17 POS Module
 
INU_CAPSTONEDESIGN_비밀번호486_업로드용 발표자료.pdf
INU_CAPSTONEDESIGN_비밀번호486_업로드용 발표자료.pdfINU_CAPSTONEDESIGN_비밀번호486_업로드용 발표자료.pdf
INU_CAPSTONEDESIGN_비밀번호486_업로드용 발표자료.pdf
 
NCERT Solutions Power Sharing Class 10 Notes pdf
NCERT Solutions Power Sharing Class 10 Notes pdfNCERT Solutions Power Sharing Class 10 Notes pdf
NCERT Solutions Power Sharing Class 10 Notes pdf
 
Chapter 3 - Islamic Banking Products and Services.pptx
Chapter 3 - Islamic Banking Products and Services.pptxChapter 3 - Islamic Banking Products and Services.pptx
Chapter 3 - Islamic Banking Products and Services.pptx
 
Salient features of Environment protection Act 1986.pptx
Salient features of Environment protection Act 1986.pptxSalient features of Environment protection Act 1986.pptx
Salient features of Environment protection Act 1986.pptx
 
Students, digital devices and success - Andreas Schleicher - 27 May 2024..pptx
Students, digital devices and success - Andreas Schleicher - 27 May 2024..pptxStudents, digital devices and success - Andreas Schleicher - 27 May 2024..pptx
Students, digital devices and success - Andreas Schleicher - 27 May 2024..pptx
 
The Roman Empire A Historical Colossus.pdf
The Roman Empire A Historical Colossus.pdfThe Roman Empire A Historical Colossus.pdf
The Roman Empire A Historical Colossus.pdf
 
Palestine last event orientationfvgnh .pptx
Palestine last event orientationfvgnh .pptxPalestine last event orientationfvgnh .pptx
Palestine last event orientationfvgnh .pptx
 
B.ed spl. HI pdusu exam paper-2023-24.pdf
B.ed spl. HI pdusu exam paper-2023-24.pdfB.ed spl. HI pdusu exam paper-2023-24.pdf
B.ed spl. HI pdusu exam paper-2023-24.pdf
 
The approach at University of Liverpool.pptx
The approach at University of Liverpool.pptxThe approach at University of Liverpool.pptx
The approach at University of Liverpool.pptx
 
Embracing GenAI - A Strategic Imperative
Embracing GenAI - A Strategic ImperativeEmbracing GenAI - A Strategic Imperative
Embracing GenAI - A Strategic Imperative
 
Introduction to Quality Improvement Essentials
Introduction to Quality Improvement EssentialsIntroduction to Quality Improvement Essentials
Introduction to Quality Improvement Essentials
 
Synthetic Fiber Construction in lab .pptx
Synthetic Fiber Construction in lab .pptxSynthetic Fiber Construction in lab .pptx
Synthetic Fiber Construction in lab .pptx
 
How to Create Map Views in the Odoo 17 ERP
How to Create Map Views in the Odoo 17 ERPHow to Create Map Views in the Odoo 17 ERP
How to Create Map Views in the Odoo 17 ERP
 
Sha'Carri Richardson Presentation 202345
Sha'Carri Richardson Presentation 202345Sha'Carri Richardson Presentation 202345
Sha'Carri Richardson Presentation 202345
 
Welcome to TechSoup New Member Orientation and Q&A (May 2024).pdf
Welcome to TechSoup   New Member Orientation and Q&A (May 2024).pdfWelcome to TechSoup   New Member Orientation and Q&A (May 2024).pdf
Welcome to TechSoup New Member Orientation and Q&A (May 2024).pdf
 
Instructions for Submissions thorugh G- Classroom.pptx
Instructions for Submissions thorugh G- Classroom.pptxInstructions for Submissions thorugh G- Classroom.pptx
Instructions for Submissions thorugh G- Classroom.pptx
 
Supporting (UKRI) OA monographs at Salford.pptx
Supporting (UKRI) OA monographs at Salford.pptxSupporting (UKRI) OA monographs at Salford.pptx
Supporting (UKRI) OA monographs at Salford.pptx
 
How libraries can support authors with open access requirements for UKRI fund...
How libraries can support authors with open access requirements for UKRI fund...How libraries can support authors with open access requirements for UKRI fund...
How libraries can support authors with open access requirements for UKRI fund...
 

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
  • 7.
  • 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
  • 20.
  • 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
  • 45.
  • 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
  • 51.
  • 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