voxxed days vienna - the why and how of reactive web-applications on the jvm
TRANSCRIPT
Who is speaking?
• freelance software consultant based in Vienna
• Vienna Scala User Group
• web, web, web
Who is speaking?
• freelance software consultant based in Vienna
• Vienna Scala User Group
• web, web, web
• writing a book on reactive web-applications
http://www.manning.com/bernhardt
Too many cores?
• "640 kb ought to be enough for anybody" ~ Bill Gates
• "4 cores ought to be enough for anybody" ~ Linus Torvalds1
1 http://highscalability.com/blog/2014/12/31/linus-the-whole-parallel-computing-is-the-future-is-a-bunch.html
Programming with many cores
• serial approach does not work
• asynchronous programming with inappropriate tools does not work drives people insane
• we need (and already have) new abstractions
• we have to re-evaluate the use of our old abstractions
The problem with mutable state
• there is no notion of time, only an illusion thereof
• changes to a mutable model only make sense locally if nobody is watching
• the larger the scope, the harder it gets to prevent inconsistencies
The problem with locks
• solution workaround for a broken conceptual model
• hard to reason about
• performance hit
Let's make things even more complicated: programming with many nodes
• scaling out to handle large loads
• scaling out / replication to handle node failure
Let's make things even more complicated: programming with many nodes
• scaling out to handle large loads
• scaling out / replication to handle node failure
• problem: networks fail
Failure is inevitable
• Jepsen series3
• CAP theorem
• transactions don't really work that way in distributed systems
3 http://aphyr.com
Hard problem to solve
• Paxos: Consensus solving protocols
• CQRS: Command Query Responsibility Segregation
• CRDTs: Commutative Replicated Data Types
Imperative programming
In imperative programming you describe how something is done
Example:List<User> users = ...List<User> minors = new ArrayList<User>();List<User> majors = new ArrayList<User>();for(int i = 0; i < users.size(), i++;) { User u = users.get(i); if(u.getAge() < 18) { minors.add(u); } else { majors.add(u); }}
Declarative programming
In declarative programming you describe what you want to get
Example:val (minors, majors) = users.partition(_.age < 18)
Declarative programming
In declarative programming you describe what you want to get
Example:val (minors, majors) = users.partition(_.age < 18)
Declarative programming is like driving with a GPS navigation system
You can do without, but it's so comfortable
Immutability
case class Car(brand: String, position: Int)
val car = Car(brand = "DeLorean", position = 0)val movedCar = car.copy(position = 10)
Immutability
case class Car(brand: String, position: Int)
val car = Car(brand = "DeLorean", position = 0)val movedCar = car.copy(position = 10)
Immutability
case class Car(brand: String, position: Int)
val car = Car(brand = "DeLorean", position = 0)val movedCar = car.copy(position = 10)
Functions and higher-order functionsval isMinor = (age: Int) => age < 18
val (minors, majors) = users.partition(isMinor)
Functions and higher-order functionsval isMinor = (age: Int) => age < 18
val (minors, majors) = users.partition(isMinor)
Moving behaviour around instead of moving data around
Transforming dataval addresses = users.filter(_.age > 18) .map(_.address) .sortBy(_.city)
Goal: To build increasingly complex behaviour through a series of transformations / by composing functions
Compositiondef fetchUser(id: Long): Option[User] = ...def fetchCar(id: Long): Option[Car] = ...
val carPrice: Option[BigDecimal] = for { user <- fetchUser(42) car <- fetchCar(23)} yield { user.age + car.price}
Compositiondef fetchUser(id: Long): Future[User] = ...def fetchCar(id: Long): Future[Car] = ...
val carPrice: Future[BigDecimal] = for { user <- fetchUser(42) car <- fetchCar(23)} yield { user.age + car.price}
Compositiondef fetchUser(id: Long): Try[User] = ...def fetchCar(id: Long): Try[Car] = ...
val carPrice: Try[BigDecimal] = for { user <- fetchUser(42) car <- fetchCar(23)} yield { user.age + car.price}
Compositiondef fetchUser(id: Long): [User] = ...def fetchCar(id: Long): [Car] = ...
val carPrice: [BigDecimal] = for { user <- (42) car <- (23)} yield { user.age + car.price}
Maths FTW!
• Option, Future, Try all implement monadic operations2
• set of data structures following the same laws
• know one, know them all
• keeping things DRY
• also, it's not that scary
2 https://www.haskell.org/haskellwiki/Monad_tutorials_timeline
Design goals• Full interoperability with Java
• Cut down boilerplate
• Pure object orientation & functional programming
• Move away from null
• Many-core programming
Play history
• MVC framework, inspired by RoR, Django, Symfony
• Zenexity
• first version released in 2009
• version 2.0 released in 2012, core rewritten in Scala
Design Principles• everything is compiled
• non-blocking I/O
• controller actions are functions (request => response)
• "share nothing" => horizontal scalability
Threaded servers
• like a train station with multiple tracks
• station chief decides which trains go on which platform
• if there are more trains than platforms, trains queue up
• if too many trains are queuing up, huge delays occur and passengers go home
Evented servers
• like a waiter in a restaurant
• runs back and forth between tables and the kitchen
• does only small tasks that do not take much time
• one server can each serve many tables at once
Advantages of the evented approach• less threads means less memory
• better CPU utilization (reduced context switching)
• (much) higher throughputs than threaded servers
History
• first release in January 2010
• based on the Actor model (Erlang)
• message-based asynchronous concurrency toolkit
• object-oriented programming done right
History
• first release in January 2010
• based on the Actor model (Erlang)
• message-based asynchronous concurrency toolkit
• object-oriented programming done right
• Akka is also a mountain in Sweden
Sending and receiving messagescase class Script(text: String)
class AudreyHepburn extends Actor { def receive = { case Script(text) => read(text) }}
Sending and receiving messagescase class Script(text: String)
class AudreyHepburn extends Actor { def receive = { case Script(text) => read(text) }}
val audrey = ActorSystem.actorOf(Props[Audrey])
audrey ! Script(breakfastAtTiffany)
Supervision
class HollyCrazyCatLady extends Actor {
lazy val cats: ActorRef = context .actorOf[Cat] .withRouter( RoundRobinRouter(nrOfInstances = 42) )
}
Supervision
class HollyCrazyCatLady extends Actor {
lazy val cats: ActorRef = context .actorOf[Cat] .withRouter( RoundRobinRouter(nrOfInstances = 42) )
override def supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 3) { case t: Throwable => log.error("A cat had a problem!", t) Restart }}
CQRS
• high level: separate writes & reads (performance)
• transform and store everything as events (write only)
• transform into query model in a separate store
Immutability (again!)
Summary
• many-core is here to stay
• FP is essential to take advantage of many-core systems
• Play and Akka make it possible to build web-applications that can scale in and out