Share
Embed
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
Transcript
69 slides
-
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 -
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 -
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 -
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 -
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 -
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> -
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)) } -
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' }); }]); -
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 -
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 -
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 -
Enterprise challenge — data analysis 17 Slow Fast Flakey Reliable
Optional Critical Speed Reliability Importance -
Enterprise challenge — data analysis 18 Slow Reliable Critical Fast
Flakey Optional Fast Reliable Critical Product Availability Service Reviews & Comments Service Order Placement Service -
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 -
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 ? ? -
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 -
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 -
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 -
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 -
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) -
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 -
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! -
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 -
Asset pipelines 31 • Example asset pipeline • source file
→ • step 1: linting → • step 2: uglification/minification → • step 3: fingerprinting → • step 4: concatenation (optional) -
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 -
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 -
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 -
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 -
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 -
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 -
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) -
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] -
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 -
- Derek Wyatt, Akka Concurrency “If you want to parallelize
a very deterministic algorithm, futures are the way to go.” -
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)) } -
- Derek Wyatt, Akka Concurrency “Add behaviour to an algorithm
by inserting actors into the message flow.” -
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(...) } } } -
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) } -
- 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.” -
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 } } -
How to handle failure? 52 package object circuitbreaker { def
catchOpenCircuitBreaker(r: Future[Result]): Future[Result] = { r recover { case _: CircuitBreakerOpenException => { Results.InternalServerError(…) } } } } -
Controller-tier 53 import circuitbreakers.BlockingCircuitBreaker.circuitBreaker.withCircuitBreaker val futureResult = withCircuitBreaker({…}) catchOpenCircuitBreaker {
futureResult.map { r => Results.Ok(…) } } -
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 -
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 -
If your code is completely non-blocking, you can simply use
the default ExecutionContext. Otherwise, you need to tune. -
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. -
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”) } -
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 -
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 -
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 -
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! -
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