Play Framework 2

The Good, The Bad and The Ugly

or

Musings of a frustrated Rails dev

Me

  • Michael Shaw
  • Scala for 3 years (Scalatra/Machine Learning/LWJGL)
  • Ruby on Rails for 5 years (love & hate)
  • Somewhat functional

What is Play?

  • Scala based MVC Framework (BYO M)
  • Emphasis on maintaining types
  • Smells a little like ASP.NET MVC
  • Very modular
  • Relatively agnostic
  • SBT based projects

What are we going to cover?

  • MVC in 15 seconds
  • A brief tour of vanilla Play
  • Play Breakdown
  • MongoDB + Salat

Routed MVC

Model View Controller

Invented by Microsoft in 2009

What we're looking for
(a way out of the tar pit)

  • Correctness (informal reasoning, type checking & testing)
  • Simplicity (according to Hickey)
  • Productivity (DRY, convention, libraries)
  • Developer Ecosystem
  • Enjoyment
  • Tool Support
  • Performance & Ops

What I'm also looking for

  • HAML/JADE (I'm sick of closing tags)
  • SASS
  • Decent forms
  • REST Support
  • Code based routing
  • An opinion of some sort
  • Good out of the box

SASS

Syntatically Awesome Stylesheets

JADE

Neater dialect of HAML, a DRY whitespace significant markup language

REST

HTTP Verb Path action used for
GET /photos index list of all photos
GET /photos/new new return a form for creating a new photo
POST /photos create create a new photo
GET /photos/:id show display a specific photo
GET /photos/:id/edit edit return a form for editing a photo
PUT /photos/:id update update a specific photo
DELETE /photos/:id destroy delete a specific photo

Onward with the tour - Setup

  • Download the play package
  • Add it to your path
  • play new my_blog
  • play

Routing

Play Style

Request => Request => Result


GET     /                           controllers.Posts.index
GET     /posts                      controllers.Posts.index
GET     /posts/new                  controllers.Posts.newPost
GET     /posts/:id                  controllers.Posts.show(id: ObjectId)
POST    /posts                      controllers.Posts.create
GET     /posts/:id/edit             controllers.Posts.edit(id: ObjectId)
POST    /posts/:id/update           controllers.Posts.update(id: ObjectId)
DELETE  /posts/:id                  controllers.Posts.destroy(id: ObjectId)

... and we're not even up to comments or users yet

What it is

  • Statically compiled to a .class, reverse routing is just a function call away
    routes.Posts.index()
  • Verbose (painfully so)
  • Safe
  • Not very expressive (no free form Scala)
  • Lacking convention (list/index/all/search & display/show/view)
  • No REST support
  • Ordering can be finicky

Routing

Rails Style


BlogApplication::Application.routes.draw do
  resources :users
  resources :posts do
    resources :comments
  end
  root to: 'posts#index'
end
          

Convention based, DRY, unsafe less safe and driven by voodoo "idiomatic" Ruby

Play Gives Us

  • Routes statically compiled against the controllers
  • A modular architecture allowing us to replace the default router


Play Doesn't Give Us

  • Convention first routes (or the ability to make them) with fallback
  • Implementation in Plain Old Scala (Request => Request => Result)
  • Extendability (without replacing the router)


Out of the box is crucial, it sets the tone for the developer community.


HLists anyone?

Controllers

Request => Result


object Posts extends ApplicationController {
def index = Action { implicit request =>
  val posts = Post.findAll().map { p =>
    p.copy(author = User.dao.findOneById(p.userId))
  }
  Ok(views.html.posts.index(posts))
}
}
          

Hello n+1 Queries

What it is

  • Functional, composable, elegant
  • Hard to mock (routes are tightly coupled to a function)
  • Safe
  • Simple (except for form handling)
  • def apply(block: Request[AnyContent] => Result)
  • A couple of minor type oddities

Custom Actions

Authenticated


def Authenticated(f:AuthenticatedRequest => Result) : Action[AnyContent] = {
Action { request =>
  request.session.get("username").flatMap { username =>
    User.findOneByUsername(username).map { user =>
      f(AuthenticatedRequest(user, request))
    }
  }.getOrElse {
    Redirect(routes.Session.newSession()).flashing("success" -> "You need to login.")
  }
}
}
          

Views


@(posts: Iterator[Post])(implicit request: AuthenticatedRequest)
@application("Posts index", Some(request.user)) {
<h1>Posts</h1>
<div class="posts">
    @for(p <- posts) {
      <article class="post">
          <h4><a href="@routes.Posts.show(p.id)">@p.title</a> (by @p.author.get.username)</h4>
      </article>
    }
</div>
}
          

What it is

  • Razor syntax from ASP.NET MVC3
  • Statically compiled to simple functions, type checking works well
    Ok(views.html.posts.index(posts))
  • Best possible implementation of a view language with closing tags ... did I mention I hate closing tags?
  • Using Scalate (for Jade/Scaml support) breaks type safety & forms
    
    def render(path: String,
               attributeMap: Map[String, Any] = Map()): Unit
                  
  • Play SASS plugin doesn't work out of the box (small bug in file renaming)

What do we need?

Jade + statically typed view interface

Form Binding

or

The bane of statically typed web frameworks


The controller side: parsing query string/POST in to something useful

Definining your form


case class UserSession(username: String, password:String)
val loginForm = Form(
mapping(
  "username" -> text(minLength = 4),
  "password" -> text(minLength = 4)
) (UserSession.apply) (UserSession.unapply).verifying(
  "Invalid username or password",
  u => User.findWithUsernameAndPassword(u.username, u.password).isDefined
)
)

At it's core


case class Form[T](mapping: Mapping[T],
                 data: Map[String, String],
                 errors: Seq[FormError],
                 value: Option[T])

All the action happens in the mapping. I'd show you the type signatures but ...

def mapping[R, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18](a1: (String, Mapping[A1]), a2: (String, Mapping[A2]), a3: (String, Mapping[A3]), a4: (String, Mapping[A4]), a5: (String, Mapping[A5]), a6: (String, Mapping[A6]), a7: (String, Mapping[A7]), a8: (String, Mapping[A8]), a9: (String, Mapping[A9]), a10: (String, Mapping[A10]), a11: (String, Mapping[A11]), a12: (String, Mapping[A12]), a13: (String, Mapping[A13]), a14: (String, Mapping[A14]), a15: (String, Mapping[A15]), a16: (String, Mapping[A16]), a17: (String, Mapping[A17]), a18: (String, Mapping[A18]))(apply: Function18[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, R])(unapply: Function1[R, Option[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18)]]): Mapping[R] = {
  ObjectMapping18(apply, unapply, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18)
}

Form Interface


def bind(data: Map[String, String]): Form[T]

def bindFromRequest()(implicit request: play.api.mvc.Request[_]): Form[T]

def fill(value: T)

def fold[R](hasErrors: Form[T] => R, success: T => R): R

What it is

  • Type safe, immutable with zero voodoo (seeing a pattern?)
  • Apply and Unapply functions (tuples to objects and back again)
  • Emphasis on form side validations
  • Reliance on ordering in binding functions makes form re-use brittle
  • Slightly awkward to use with immutable models due to binding functions being specified at form definition (I might be doing it wrong)

What we need

???

Is it lenses?

Salat + MongoDB

MongoDB: Database with JSON documents instead of rows

Salat: Immutable Case Class only ORM for MongoDB


case class User(id: ObjectId = new ObjectId,
              username: String,
              password: String,
              added: Date = new Date(),
              updated: Option[Date] = None,
              deleted: Option[Date] = None)
          

Define your provider


object User extends ModelCompanion[User, ObjectId] {
val collection = mongoCollection("users")
val dao = new SalatDAO[User, ObjectId](collection = collection) {}

def findWithUsernameAndPassword(username:String,
                                password:String) :Option[User] = {
  dao.findOne(MongoDBObject("username" -> username, "password" -> password))
}
}
          

In Action


val steve = User(username = "steve",
               password = "please")
User.save(steve)

Post.dao.findOneById(id).map { post =>
val updatedPost = post.copy(title = value.title,
                          content = value.content)
Post.save(updatedPost)
}

Simple & Immutable


> db.posts.findOne();
{
"_id" : ObjectId("5020efd803646a16c93b2f9a"),
"userId" : ObjectId("5020efd803646a16c93b2f99"),
"title" : "New Apple Products",
"content" : "content for New Apple Product",
"tags" : [ ],
"comments" : [
  {
    "userId" : ObjectId("5020efd803646a16c93b2f99"),
    "content" : "I love this product."
  }
]
}

Developer Ecosystem

  • Google Group/Stack Overflow is relatively active
  • Favoured by Typesafe, it's part of the stack
  • Modules scene is a mixed affair
  • Momentum is paramount due to network effects

Tooling

  • Eclipse & IDEA project generators out of the box (works great)
  • IDEA view support is poor, keep Sublime Text open for markup
  • SBT based

Scala Based Alternatives

  • Lift Hiring people to type out HTML quickly?
  • Digging up old Java Frameworks?
  • Scalatra
  • SOA approach with Scalatra/Blue Eyes/Spray/Unfiltered and use client side rendering

Play 2

The Good, The Bad and The Ugly The Vanilla?

  • Statically Typed
  • Functional & Immutable
  • No Voodoo
  • No Ugly
  • Agnostic

Thank you