42
Principles of the Play Framework Bernhard Huemer

Principles of the Play framework

Embed Size (px)

Citation preview

Page 1: Principles of the Play framework

Principles of the Play Framework

Bernhard Huemer

Page 2: Principles of the Play framework

Why are we here?

Page 3: Principles of the Play framework

Composition

Page 4: Principles of the Play framework

Contextual vs. composable design

• Boundaryless extensions / flexibility

• Steep learning curve

See also: http://nealford.com/memeagora/2013/01/22/why_everyone_eventually_hates_maven.html

• Anticipated extension points

• Easy to get into / quick wins

Page 5: Principles of the Play framework

Referential transparency

• No reassignments of variables

• No setters, no mutations

• No exceptions (no throw new …)

• No console input / output

• No network communication

• ….

"An expression is said to be referentially transparent if it can be replaced with its value without changing the behaviour of a

program."

Page 6: Principles of the Play framework

Side-effects don’t composeprotected void renderBasket(HttpServletResponse response) throws ServletException, IOException { try (PrintWriter writer = response.getWriter()) { writer.println(“HTML for our basket.”); } } !protected void renderProductList(HttpServletResponse response) throws ServletException, IOException { try (PrintWriter writer = response.getWriter()) { writer.println(“HTML for our products list.”); } }

Best practice: Don’t assume any special context when calling these methods, it will manage its resources itself.

Page 7: Principles of the Play framework

Side-effects don’t composeprotected void renderAll(HttpServletResponse response) throws ServletException, IOException { renderBasket(response); renderProductList(response); }

• Composing code with side-effects always requires additional “plumbing”

• in this case: close() method of the response

• Ignore the <head />, <body /> HTML issues ..

Page 8: Principles of the Play framework

What’s wrong here?import io.Source.fromFile val it: Iterator[String] = fromFile(“foo.txt").getLines // or from the network !var result = 0 while (it.hasNext) { val line = it.next result += line.toInt }

General idea: Some part of the application produces a stream of something that we want to consume elsewhere.

Page 9: Principles of the Play framework

What’s wrong here?import io.Source.fromFile val it: Iterator[String] = fromFile(“foo.txt").getLines // or from the network !var result = 0 while (it.hasNext) { val line = it.next result += line.toInt }

val it: Iterator[String] = fromFile("foo.txt").getLines var result = 0 it foreach { line => result += line.toInt }

val it: Iterator[String] = fromFile("foo.txt").getLines var result = 0 it foldLeft(0) { case (acc, line) => acc + line.toInt }

Page 10: Principles of the Play framework

Problems …• Repetitive pattern (DRY principle)

• Manual pulling, possibly blocking I/O

• No error handling (sometimes we forget, right?)

• No communication with the producer (e.g. back pressure)

• Resource handling (how long do we need a resource and who is responsible for opening/closing/recovering it?)

• Missing or rather difficult composability

• What if the input source was infinite? See: http://www.slideshare.net/afwlehmann/iteratees-intro

Page 11: Principles of the Play framework

Architecture at LinkedIn

See: http://www.slideshare.net/brikis98/play-framework-async-io-with-java-and-scala

Page 12: Principles of the Play framework

Latency spilled over to the supposedly fast part

Clarification: Latency in one part of the network will slow down anything that depends on it directly or indirectly, it should, however, not have an impact on anything else!

Page 13: Principles of the Play framework

Requests are functionsobject Application extends Controller { def greet(name: String) = Action { request => Ok(s"<html>Hello $name!</html>") .as("application/xhtml+xml") } }

GET /greet/:name controllers.Application.greet(name)

• Controller is not really required as base class

• Action { } is like a factory method that produces one of these functions

Page 14: Principles of the Play framework

Play in the command linescala> controllers.App.greet(“ConFESS”) res0: play.api.mvc.Action[play.api.mvc.AnyContent] = Action(parser=BodyParser(anyContent)) !scala> controllers.App.greet(“ConFESS”).apply( play.api.test.FakeRequest() ) res1: scala.concurrent.Future[play.api.mvc.Result] = scala.concurrent.impl.Promise$KeptPromise@3ffe0466

Read-eval-print-loop (REPL): Scala, like most scripting languages, comes with an interactive shell that you can use to test your application. Play supports this tool as well.

Page 15: Principles of the Play framework

Your application code never uses the network directly

scala> val result = Await.result( controllers.App.greet(“ConFESS”).apply( play.api.test.FakeRequest() ), atMost = 2.seconds ) result: play.api.mvc.Result = Result(200, Map( Content-Type -> application/xhtml+xml; charset=utf-8) )

• No HTTP server we needed to start

• Nothing we needed to mock *

* I will not count FakeRequest .. it’s infinitely simpler than mocking servlet requests

Page 16: Principles of the Play framework

Side-effects are collected until the very end of the request

scala> result.body.run(Iteratee.foreach({ bytes => println(new String(bytes)) })) <html>Hello ConFESS!</html> res8: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@3ee2e

• The framework takes care of “side-effectful” code once you returned control to it

Page 17: Principles of the Play framework

Minimises incorrect usage“I was eventually persuaded of the need to design programming notations so as to

1. maximise the number of errors which cannot be made, or

2. if made, can be reliably detected at compile time.“

- Tony Hoare (ACM Turing Award Lecture)

• No IllegalStateExceptions in the API

• PrintWriters and OutputStreams

• Redirects after status codes / headers / etc..

• Headers after the response body was started

Page 18: Principles of the Play framework

Composition of functionsdef Logging[A](action: Action[A]): Action[A] = Action.async(action.parser) { request => Logger.info("Calling action") action(request) }

def index = Logging { Action { Ok("Hello World") }}

No mental overhead: Simplistic example (think security, instead), but the point is: No additional concepts you need to learn!

Page 19: Principles of the Play framework

private def futureInt(): Future[Int] = Future { intensiveComputation() } !def index = Action.async { futureInt().map({i => Ok("Got result: " + i) }) }

Small detour: Asynchronous HTTP programming

Futures: An object that acts as a proxy for a result that is initially unknown, because the computation of its value is not yet complete.

Page 20: Principles of the Play framework

Sequential composition

def fetch(url: String): Future[Resource] = ???def getThumbnail(url: String): Future[Resource] = fetch(url) flatMap { page => fetch(page.imageLinks()(0)) }

trait Future[A] { def map[B](f: A => B): Future[B] def flatMap[B](f: A => Future[B]): Future[B] ... }

Page 21: Principles of the Play framework

Concurrent composition

object Future { … def collect[A](fs: Seq[Future[A]]): Future[Seq[A]] def join(fs: Seq[Future[_]]): Future[Unit] def select(fs: Seq[Future[A]]) : Future[(Try[A], Seq[Future[A]])] }

Page 22: Principles of the Play framework

“All non-trivial abstractions, to some degree, are leaky.”

- Joel Spolsky

Page 23: Principles of the Play framework

What about streaming?

Problem with our abstraction: With this simple abstraction we’d have to have both fully in-memory: the request and the response body.

Page 24: Principles of the Play framework

trait EssentialAction { def apply(headers: RequestHeaders): Iteratee[Array[Byte], Result] }

The ♥ of the framework (1)

RequestHeaderPath, method (GET / POST),

headers, cookies, etc., but not the request body itself!

Iteratee[A, B]

Repeatedly and asynchronously consumes instances of A to

eventually produce an instance of B

Page 25: Principles of the Play framework

final class ResponseHeader( val status: Int, headers: Map[String, String] = Map.empty)

case class Result( header: ResponseHeader, body: Enumerator[Array[Byte]], connection: HttpConnection.Connection = HttpConnection.KeepAlive) { /* ... */ }

The ♥ of the framework (2)

Iteratee[A, B] Repeatedly and asynchronously consumes instances of A to eventually produce an instance of B

Enumerator[A] Repeatedly and asynchronously produces instances of A

Page 26: Principles of the Play framework

“Convenient proxy factory bean superclass for proxy factory beans that create only singletons. !Manages pre- and post-interceptors (references, rather than interceptor names, as in ProxyFactoryBean) and provides consistent interface management.”

How many concepts can you keep track of?

Page 27: Principles of the Play framework

Iteratees and Enumerators

Reactive Streams, RX, Conduits, Process, …

Page 28: Principles of the Play framework

Enumerators

trait Enumerator[E] { def run[A](it: Iteratee[E, A]): Iteratee[E, A] } !sealed trait Input[+T] case class El[T](x: T) extends Input[T] case object Empty extends Input[Nothing] case object EOF extends Input[Nothing]

• Stream producers that push input into consumers

Page 29: Principles of the Play framework

Iteratees

sealed trait Iteratee[I, O] !case class Done[I, O](result: O, remainingInput: Input[I]) case class Cont[I, O](k: Input[I] => Iteratee[I, O]) case class Error[I, O](t: Throwable)

• Stream consumers, consume chunks at a time

State machine for iterations: If we were to draw a state machine for iterations, this is what we would get.

Page 30: Principles of the Play framework

Example: Counting characters

def charCounter(count: Int = 0): Iteratee[String, Int] = Cont[String, Int] { case El(str) => charCounter(count + str.length) case Empty => charCounter(count) case EOF => Done(count) }

def countChars(it: Iterator[String]): Int = { var count = 0 while (it.hasNext) { count = count + it.next.length } count }

Why bother: Iteration can be paused / resumed at any moment without losing the current state.

Page 31: Principles of the Play framework

Example: Communication with the producer

def moreThanChunks[A](number: Int): Iteratee[A, Boolean] = Cont[A, Boolean] { case input if number <= 0 => Done(true, input) case EOF => Done(false, EOF) case El(_) => moreThanChunks(number - 1) case Empty => moreThanChunks(number) }

def moreThan[A](number: Int, it: Iterator[A]): Boolean = { var i = number while (it.hasNext) { it.next // not needed if (i <= 0) { return true } i -= 1 } return false }

Why bother: Done indicates to the producer that it can close the resource (e.g. no need to wait until the last hasNext).

Page 32: Principles of the Play framework

Example: Enumeratordef enumerate[E](elems: Iterator[E]): Enumerator[E] = new Enumerator[E] { def run[E, A](it: Iteratee[E, A]): Iteratee[E, A] = it match { case Cont(k) => { val input = if (elems.hasNext) { El(elems.next) } else { EOF } // recursively call this method again with the next Iteratee run(k(input)) } // here we could also do resource clean-up, if necessary case _ => it } }

Complicated? Yes, I know, this isn’t particularly obvious stuff ..

Page 33: Principles of the Play framework

Streams work just like collectionsdef charCounter(count: Int = 0): Iteratee[String, Int] = Cont[String, Int] { case El(str) => charCounter(count + str.length) case Empty => charCounter(count) case EOF => Done(count) }

def fold[A, B](acc: B)(f: (A, B) => B): Iteratee[A, B] = Cont[String, Int] { case El(a) => fold(f(a, acc))(f) case Empty => fold(acc)(f) case EOF => Done(acc) }

def charCounter: Iteratee[String, Int] = fold(0)({ case (str, count) => count + str.length }

No mental overhead: They really behave in the same way, really nothing new we’re learning here (I’m sorry ..)

Page 34: Principles of the Play framework

Asynchronous streamssealed trait Iteratee[I, O] !case class Done[I, O](result: O, remainingInput: Input[I]) case class Cont[I, O](k: Input[I] => Future[Iteratee[I, O]]) case class Error[I, O](t: Throwable)

def fold[A,B](acc: B)(f: (A, B) => B): Iteratee[A, B] = ...def foldM[A,B](acc: B)(f: (A, B) => Future[B]): Iteratee[A, B] = ...

Composing concepts: The concept of Futures and Iteratees can be combined rather easily to provide reactive streams.

Page 35: Principles of the Play framework

Example: Using asynchronous combinators

def expensiveComputation(str: String): Future[Int] = ???!def processExpensive: Iteratee[String, Int] = foldM(0)({ case (str, acc) => expensiveComputation(str) map { acc + _ } })

Back-pressure for free (almost): Enumerators won’t push more elements into this Iteratee than it can handle.

Page 36: Principles of the Play framework

Composability for handling streams of data

def isAroundLocation(tweet: Tweet, loc: Location): Booleandef retweetsGreaterThan(tweet: Tweet, retweets: Int): Booleandef userForTweet(tweet: Tweet): Future[User]val tweets: Enumerator[Tweet] = // … !tweets .through(Enumeratee.filter({ tweet => isAroundLocation(tweet, Location.Vienna) }) .through(Enumeratee.filter({ tweet => retweetsGreaterThan(tweet, 50) }) .through(Enumeratee.mapM({ tweet => userForTweet(tweet) }) .run(Iteratee.foreach({ user => println(s“User $user tweeted something popular in Vienna.”) })

Page 37: Principles of the Play framework

void onWritePossible(){ while (isReady()){ out.write("<H1>Hello</H1>"); } }

Async Servlets

• Iterative processing while stream is available

• Callbacks otherwise

Page 38: Principles of the Play framework

What’s the problem here?

void onWritePossible(){ while (isReady()){ out.write(“<H1>Hello</H1>"); out.write(“<H1>World</H1>”); } }

Async I/O is hard: Side-effects in the API are kind of necessary, but they make it very hard to use.

Page 39: Principles of the Play framework

boolean flag = true; !void onWritePossible(){ while (isReady()){ if (flag) { out.write(“<H1>Hello</H1>"); } else { out.write(“<H1>World</H1>"); } flag = !flag; } }

Async I/O is hard …

Enumerator.interleave( Enumerator.repeat(“Hello”), Enumerator.repeat(“World”) )

.. it doesn’t need to be though, if you make your API composable.

See: https://blogs.oracle.com/theaquarium/entry/javaone_replay_into_the_wild

Page 40: Principles of the Play framework

Conclusion

• Build your APIs like Lego blocks

• Stay away from side effects as much as possible

• Deferred execution / interpretation allows you to do that

Page 41: Principles of the Play framework

Book recommendation

Page 42: Principles of the Play framework

Q & A

Bernhard Huemer @bhuemer