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. Play 2.x
    Overview, architecture, and advanced features

    View Slide

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

    View Slide

  3. Intro

    View Slide

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

    View Slide

  5. A hypothetical application architecture

    View Slide

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

    View Slide

  7. Interface architecture

    View Slide

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

    View Slide

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

    View Slide

  10. 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
    @()
    ...


    ...



    ...


    View Slide

  11. 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))
    }

    View Slide

  12. 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'
    });
    }]);

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  16. Designing a responsive UI with Play.

    View Slide

  17. Enterprise challenge — data analysis
    17
    Slow Fast
    Flakey Reliable
    Optional Critical
    Speed
    Reliability
    Importance

    View Slide

  18. Enterprise challenge — data analysis
    18
    Slow Reliable Critical
    Fast Flakey Optional
    Fast Reliable Critical
    Product Availability
    Service
    Reviews & Comments
    Service
    Order Placement
    Service

    View Slide

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

    View Slide

  20. 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
    ? ?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. Defining your first routes and controllers

    View Slide

  26. 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)

    View Slide

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

    View Slide

  28. 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!

    View Slide

  29. Asset pipeline and sbt-web

    View Slide

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

    View Slide

  31. Asset pipelines
    31
    • Example asset pipeline
    • source file →
    • step 1: linting →
    • step 2: uglification/minification →
    • step 3: fingerprinting →
    • step 4: concatenation (optional)

    View Slide

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

    View Slide

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

    View Slide

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

    • 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

    View Slide

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

    View Slide

  36. Model-tier and data access

    View Slide

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

    View Slide

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

    View Slide

  39. 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)

    View Slide

  40. 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]

    View Slide

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

    View Slide

  42. Futures, Actors, and WebSockets

    View Slide

  43. - Derek Wyatt, Akka Concurrency
    “If you want to parallelize a very deterministic algorithm, futures
    are the way to go.”

    View Slide

  44. 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))
    }

    View Slide

  45. - Derek Wyatt, Akka Concurrency
    “Add behaviour to an algorithm by inserting actors into the
    message flow.”

    View Slide

  46. 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(...)
    }
    }
    }

    View Slide

  47. 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)
    }

    View Slide

  48. Circuit Breaker Pattern

    View Slide

  49. - 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.”

    View Slide

  50. Circuit Breaker
    50
    Closed
    Open
    Half-
    Open
    attempt
    reset
    trip reset

    View Slide

  51. 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
    }
    }

    View Slide

  52. How to handle failure?
    52
    package object circuitbreaker {
    def catchOpenCircuitBreaker(r: Future[Result]): Future[Result] = {
    r recover { case _: CircuitBreakerOpenException => {
    Results.InternalServerError(…)
    }
    }
    }
    }

    View Slide

  53. Controller-tier
    53
    import circuitbreakers.BlockingCircuitBreaker.circuitBreaker.withCircuitBreaker
    val futureResult = withCircuitBreaker({…})
    catchOpenCircuitBreaker {
    futureResult.map { r =>
    Results.Ok(…)
    }
    }

    View Slide

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

    View Slide

  55. ExecutionContext tuning

    View Slide

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

    View Slide

  57. - James Ward
    “Lots of various knobs you can turn to tweak.”

    View Slide

  58. If your code is completely non-blocking, you can simply use the
    default ExecutionContext.
    Otherwise, you need to tune.

    View Slide

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

    View Slide

  60. 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”)
    }

    View Slide

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

    View Slide

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

    View Slide

  63. Testing

    View Slide

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

    View Slide

  65. 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!

    View Slide

  66. Coming soon to Play 2.4

    View Slide

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

    View Slide

  68. Questions?

    View Slide

  69. ©Typesafe 2014 – All Rights Reserved

    View Slide