Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Reactive Web Development with Play 2.3

Reactive Web Development with Play 2.3

We cover every aspect of building a reactive web application with Play 2.3, including integration with AngularJS. We also cover the fundamentals of reactive programming including the importance of embracing futures for async, non-blocking integration.

Kevin Webber

May 19, 2015
Tweet

More Decks by Kevin Webber

Other Decks in Programming

Transcript

  1. Topics, goals, context 2 • An introduction to core features

    of Play 2.x • Interface architecture and integration options • Advanced code techniques to create Reactive web applications
  2. Play 2.3 overview 4 • Stateless MVC framework with routing,

    a template engine, REST support, etc • Dynamic class reloading • Support for Java 8 and Scala 2.1x • sbt-web to implement an asset pipeline • Akka interoperability for highly concurrent web applications • Built on Netty 3.9.x
  3. 6 Play Framework nginx Home Scala Template Mobile App Android

    Load Balancer e.g, Netscaler Back-end Services Admin Pure Scala Views Checkout Scala Template REST API Services CSS JS AngularJS Views & Routing AngularJS Views & Routing • Hypothetical architecture with logically-hosted SPAs • Mix of AngularJS, pure Play views, and a RESTful API • Load balancer to scale requests across a cluster of Play servers • Static assets served off of nginx • A mobile app that consumes services
  4. Play combined with a modern SPA framework 8 • Single-page

    web applications — SPAs — provide a responsive user experience • Continue to work with Play in a familiar way — e.g, Play handles authorization/authentication before loading a SPA • Decouples UI from business logic using a services-based application architecture • Examples use AngularJS routing and controllers at a SPA level
  5. 9 Simplified HTTP request 1. mydomain.com/admin/settings renders views.html.admin HTML 2.

    Browser fetches static assets served from outside of Play 3. Ajax requests — Play serves JSON via a RESTful API Browser Play Akamai, nginx, etc HTTP GET (mydomain.com/admin/settings) HTML for views.html.admin(...) JS, CSS, images, etc HTTP GET (mydomain.com/ws/some/services) JSON
  6. 10 • Scala template rendered by Play controller • HTML

    inserted into ng-view by Angular controller after initial page render HEADER Play handles navigation SECTION ng-app directive - Angular handles navigation FOOTER @() <head>...</head> <body> <div class="wrapper"> <header>...</header> <section id="body" ng-app="ngAdmin"> <div ng-view></div> </section> <footer>...</footer> </div> </body> </html>
  7. Controller with index and AngularJS wildcard actions 11 /** *

    Renders the admin template. */ def index = Action.async { implicit request => val account = loggedIn Ok(views.html.admin()).withCookies(Cookie(“…”, …, httpOnly = false)) } /** * Renders the admin template and captures the URI for Angular “pass-throughs”. * We can perform additional actions on the URI before rendering the template. */ def angularWildcard(uri: String) = Action.async { implicit request => val account = loggedIn Ok(views.html.admin()).withCookies(Cookie(“…”, …, httpOnly = false)) }
  8. AdminConfig.js 12 ngAdmin.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { $locationProvider.html5Mode(true); $routeProvider.when('/admin', {

    templateUrl: '/assets/html/admin/index.html', controller: 'AdminCtrl' }); $routeProvider.when('/admin/settings', { templateUrl: '/assets/html/admin/settings.html', controller: 'AdminCtrl' }); }]);
  9. Advantages of logical composition of SPAs with Play 13 •

    Continue to use Scala view templates in Play • Pre-render some data — reduces HTTP traffic • Server-side developers responsible for overall application architecture • Front-end developers “plug-in” their components
  10. Why not a single SPA or only Scala view templates?

    14 Smaller cross-functional teams • A large e-commerce project might have a checkout team, a product information team, an admin team, etc • UI teams form across conceptual boundaries • FYI — walmart.ca had a 3-1 ratio of JavaScript & UI code compared to Scala code
  11. Why not a single SPA or only Scala view templates?

    15 Different use cases • Admin page used by 20 employees, vs… • Home-page with reactive content driven by SSE
  12. Enterprise challenge — data analysis 17 Slow Fast Flakey Reliable

    Optional Critical Speed Reliability Importance
  13. Enterprise challenge — data analysis 18 Slow Reliable Critical Fast

    Flakey Optional Fast Reliable Critical Product Availability Service Reviews & Comments Service Order Placement Service
  14. Options for speed, reliability, and consistency 19 Option 1 —

    Cache slow/unreliable services • Render data with initial page load • Cache to reduce uncertainty • Cache in batch mode (offline) or during requests • Essential for mandatory data from imperfect services Play Play Load Balancer Play Distributed Cache
  15. Options for speed, reliability, and consistency 20 Option 1 —

    Caching cons • Accuracy of cached data may suffer, e.g, displaying a stale out of stock flag • Avoid caching at an instance level of Play, e.g, different round robin requests returning different product information • Must keep instance level caches in sync Play Play Play Load Balancer Cache Cache Cache ? ?
  16. Enterprise challenge — data analysis 21 Option 2 — Progressively

    enhance UI • Load data asynchronously • Accurate data from slow services can be progressively added to the UI without blocking • Useful for non-mandatory data to avoid the complexity of cache syncing and cache busting cache && pre-render load async || fail whale load async || omit
  17. Enterprise challenge — data analysis 22 Option 2 — Progressively

    enhance UI cons • Data isn’t guaranteed to display within a specific timeframe… or at all • Fail whale and timeout logic must be implemented client-side cache && pre-render load async || fail whale load async || omit
  18. Part 1 — Initial HTML rendering 23 1. Initial request

    2. Dispatch to server 1 Load Balancer Play 2 3a Cache 4 Web Service 3b Client 3. Request data 4. Render HTML and return
  19. Part 2 — Progressive UI enhancement 24 1. Render initial

    HTML 2. Load static assets 3. AJAX call from AngularJS 1 3 Load Balancer Play 4 Browser Services 5 6 CDN (Akamai) 2 4. Request JSON from Play 5. Aggregate and compose 6. Return JSON, enhance page
  20. routes 26 GET /admin controllers.AdminController.index GET /admin/login controllers.AdminController.login GET /admin/*any

    controllers.AdminController.angularWildcard(any: String) GET /ws/some/service controllers.ServiceController.service • Routes must be declared in order • /admin and /admin/login are used to render Scala view templates • /admin/*any will be intercepted by client-side routing in AngularJS • Client-side routes may hit the server if the URI has been bookmarked • Nothing special about web services aside from payload (JSON vs HTML)
  21. Sample controller action 27 /** * Renders the admin template.

    */ def index = Action.async { implicit request => val account = loggedIn Ok(views.html.admin()).withCookies(Cookie(“…”, …, httpOnly = false)) } • Controllers should stay thin and delegate the real work • Controllers actions should always be async • Controllers are stateless so context must be stored in cookies or cached
  22. Asynchronous vs synchronous processing times 28 Process 1 Process 2

    Process 3 0 ms 425 ms 200 ms 75 ms 150 ms Asynchronous - 200ms Process 1 Process 2 Process 3 0 ms 425 ms Synchronous - 425ms • Staying async — and in the future — ensures that processing time is not bounded by IO • Async processing is limited by only the longest running process • Synchronous processing is limited by the combined processing times • Stay async!
  23. An intro to sbt-web 30 • sbt-web brings the notion

    of a highly configurable asset pipeline to build files pipelineStages := Seq(rjs, digest, gzip) • The above will order the RequireJs optimizer (sbt-rjs), the digester (sbt-digest) and then compression (sbt-gzip) • These tasks will execute in the order declared, one after the other
  24. Asset pipelines 31 • Example asset pipeline • source file

    → • step 1: linting → • step 2: uglification/minification → • step 3: fingerprinting → • step 4: concatenation (optional)
  25. Asset pipelines 32 • Linting • Inspect source code for

    suspicious language usage • Uglification/minification • Mangle and compress source files, e.g, remove whitespace, etc • Fingerprinting • Change the filename to reflect the contents of the file for cache busting • Concatenation • Combine multiple source files to reduce browser load times
  26. sbt-web and asset fingerprinting 33 • Asset fingerprinting makes the

    name of a file dependent on the contents of the file • HTTP headers can be set to encourage caches to keep their own copy of the content • When the content is updated, the fingerprint will change • This will cause the remote clients to request a new copy of the content • Known as cache busting
  27. Turning on asset fingerprinting 34 • Turning on asset fingerprinting

    only requires one additional route GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) • Then request each asset as versioned <link rel="stylesheet" href=“@routes.Assets.versioned('assets/images/example.png')"> • A digest file and new file will be created based on MD5 ./target/web/digest/images/23dcc403b263f262692ac58437104acf-example.png ./target/web/digest/images/example.png.md5
  28. sbt-web plugins 35 • sbt-coffeescript • sbt-concat • sbt-css-compress •

    sbt-digest • sbt-filter • sbt-gzip • sbt-handlebars • sbt-html-minifier • sbt-imagemin • sbt-jshint • sbt-jst • sbt-less • sbt-mocha • sbt-purescript • sbt-reactjs • sbt-rjs • sbt-stylus • sbt-uglify
  29. Design considerations 37 Think in verbs rather than nouns… •

    save(model) • SRP! • save is likely a simple function that does a single thing • model.save() • Violates SRP • Tougher to test • Domain models now need to be injected with mock DB connections, etc
  30. JSON support 38 • Extremely powerful and can reduce a

    significant amount of code • If working with coast-to-coast JSON, no need for DAOs, ORMs, etc • Think in terms of data transformation • Case classes to JSON, BSON, or SQL and vice versa
  31. JSON Reads/Writes example 39 • Simply define a Writes in

    implicit scope before invoking toJson implicit val locationWrites = new Writes[Location] { def writes(location: Location) = Json.obj( "lat" -> location.lat, "long" -> location.long ) } val json = Json.toJson(location)
  32. JSON Reads/Writes example 40 • Also elegantly handles Reads •

    Build in validate using standard types (e.g, Double, Int, etc) or define your own validators implicit val locationReads: Reads[Location] = ( (JsPath \ "lat").read[Double] and (JsPath \ "long").read[Double] )(Location.apply _) val json = { ... } val locationResult: JsResult[Location] = json.validate[Location]
  33. Slick example 41 • Slick is a Functional Relational Mapping

    (FRM) library for Scala where you work with relational data in a type-safe and functional way • Developers benefit from the type-safety and composability of FRM as well as being able to reuse the typical Scala collection APIs like filter, map, foreach, etc • Can also use plain SQL for insertions, complex joins, etc • http://typesafe.com/activator/template/hello-slick-2.1 // This code: coffees.filter(_.price < 10.0).map(_.name) // Will produce a query equivalent to the following SQL: select COF_NAME from COFFEES where PRICE < 10.0
  34. - Derek Wyatt, Akka Concurrency “If you want to parallelize

    a very deterministic algorithm, futures are the way to go.”
  35. Futures 44 • Great for handling immutable state • Intensive

    computations, reading from a database, pulling from web services, etc • Composition — futures guarantee order when chained • Parallelizing a deterministic algorithm def index = Action.async { val futureInt = scala.concurrent.Future { intensiveComputation() } futureInt.map(i => Ok("Got result: " + i)) }
  36. - Derek Wyatt, Akka Concurrency “Add behaviour to an algorithm

    by inserting actors into the message flow.”
  37. Actors 46 • Great for handling mutable state • Easy

    for imperative developers to dive into quickly • Don't guarantee order • Easy to change behaviour by inserting new actors into the message flow • Messages are directed to a specific actor, avoiding callback hell package actors import akka.actor._ object SomeActor { def props = Props(new GameActor) } class SomeActor extends Actor { def receive = { case request: SomeRequest => { sender ! SomeResponse(...) } } }
  38. WebSockets 47 • Play provides two different built in mechanisms

    for handling WebSockets • Actors — better for discreet messages • Iteratees — better for streams • Both of these mechanisms can be accessed using the builders provided on WebSocket def websocketAction = WebSocket.acceptWithActor[JsValue, JsValue] { request => channel => SomeActor.props(channel) }
  39. - http://doc.akka.io “A circuit breaker is used to provide stability

    and prevent cascading failures in distributed systems. These should be used in conjunction with judicious timeouts at the interfaces between remote systems to prevent the failure of a single component from bringing down all components.”
  40. BlockingCircuitBreaker.scala 51 import akka.pattern.CircuitBreaker import play.api.libs.concurrent.Akka import play.api.Play.current import scala.concurrent.duration.DurationInt

    object BlockingCircuitBreaker { val circuitBreaker = { val maxFailures = current.configuration.getInt(“properties.cb.maxFailures").getOrElse(1) val callTimeout = current.configuration.getInt(“properties.cb.callTO").getOrElse(10) val resetTimeout = current.configuration.getInt(“properties.cb.resetTO").getOrElse(100) val cb = new CircuitBreaker( executor = ThreadPools.blockingPool, scheduler = Akka.system.scheduler, maxFailures = maxFailures, callTimeout = callTimeout.milliseconds, resetTimeout = resetTimeout.milliseconds ) cb.onOpen(someAlertOperation) cb } }
  41. How to handle failure? 52 package object circuitbreaker { def

    catchOpenCircuitBreaker(r: Future[Result]): Future[Result] = { r recover { case _: CircuitBreakerOpenException => { Results.InternalServerError(…) } } } }
  42. Why circuit breakers? 54 • Prevent failures from overwhelming strained

    services and cascading • Codify strategies to address the inevitable failure of external systems • Prepare for failure early in a project
  43. ExecutionContext 56 • ExecutionContext is just an abstraction over threads

    & thread pools • Default ExecutionContext is fork-join-executor • fork-join-executor • Each thread has it’s own queue of work • If the queue runs dry the thread will steal work from other queues
  44. If your code is completely non-blocking, you can simply use

    the default ExecutionContext. Otherwise, you need to tune.
  45. application-env.conf 59 # Specific execution contexts play { akka {

    actor { default-dispatcher = { fork-join-executor { parallelism-min = 300 parallelism-max = 300 } } } } blocking-io { fork-join-executor { parallelism-min = 100 parallelism-max = 100 } } memory-hungry { fork-join-executor { parallelism-factor = 1.0 } } The parallelism factor is used to determine thread pool size using the following formula: 
 
 ceil(available processors * factor)
 
 Resulting size is then bounded by the parallelism-min and parallelism-max values.
  46. ThreadPools.scala 60 import scala.concurrent.ExecutionContext import play.libs.Akka object ThreadPools { implicit

    val hungryPool: ExecutionContext = Akka.system.dispatchers.lookup(“play.memory-hungry“) implicit val blockingPool: ExecutionContext = Akka.system.dispatchers.lookup(“play.blocking-io”) }
  47. ExecutionContext tuning 61 • Number of threads available • High

    enough for performance — saturate CPUs • Low enough that you don't run OOM • Control memory allocation when • Processing large amounts of data — e.g, pulling large JSON docs from a service • Settings depend on the type of data that you're processing
  48. ExecutionContext tuning 62 • Create different pools for different types

    of work • Intense processing & memory — smaller # of threads • Blocking IO, etc — larger # of threads • Tweak until you balance performance and memory • Bring your friendly Ops team lots of cookies
  49. Test framework comparison 64 • ScalaTest • Human readable output

    — in English • FeatureSpec • Matchers • http://www.artima.com/docs-scalatest-2.0.M8/#org.scalatest.Matchers • Great writeup by Gilt • http://tech.gilt.com/post/62430610230/which-scala-testing-tools-should-you- use
  50. Test framework comparison 65 • Specs2 • Enabled by default

    in Play • Good choice for international teams who gain less from English readability • Both Specs2 and ScalaTest are fantastic!
  51. Play 2.4 67 • Built-in dependency injection support • Experimental

    support for running Play on akka-http • Experimental support for handling IO in Java and Scala using Reactive Streams • Slick over anorm, JPA over ebean • Pull anorm/ebean support out into separate modules in GitHub playframework repo