Skip to content

Instantly share code, notes, and snippets.

@davetron5000
Created November 27, 2012 19:01
Show Gist options
  • Save davetron5000/4156273 to your computer and use it in GitHub Desktop.
Save davetron5000/4156273 to your computer and use it in GitHub Desktop.

Have seen a few play apps' source and it feels like massive boilerplate and leaky promise/future/async crud everywhere.

Lets take this:

https://github.com/guardian/frontend/blob/master/article/app/controllers/ArticleController.scala

case class ArticlePage(article: Article, storyPackage: List[Trail], edition: String)

object ArticleController extends Controller with Logging {

  def render(path: String) = Action { implicit request =>
    val promiseOfArticle = Akka.future(lookup(path))
    Async {
      promiseOfArticle.map(_.map { renderArticle }.getOrElse { NotFound })
    }
  }

  private def lookup(path: String)(implicit request: RequestHeader): Option[ArticlePage] = suppressApi404 {
    // .. stuff omitted
  }

  private def renderArticle(model: ArticlePage)(implicit request: RequestHeader): Result = // .. omitted
}

Let's look at that render method. What does it do? It's pretty hard to tell, even with a lot of the code removed. It's trying to render an article. If we just wrote some vanilla code to do that, it would be:

def render(path:String) = {
  renderArticle(lookup(path).getOrElse(NotFound))
}

So what's all that other stuff? We've got all the buzzwords covered:

  • promise
  • future
  • async

Why is this here? Several of this apps controllers have this exact structure. Shouldn't this concern be separated somehow? Even the Action DSL method that starts this off sticks out like a sort thumb. This method is all unnecessary complexity. To figure out what it does, I have to know what futures and promises are, and what Async does, but moreover, almost none of this method's source is specific to the business logic being performed. Why?

Let's see some more boilerplate:

  • implicit request - why do I have to type this in every controller? Shouldn't every controller have a request without me typing code?!
  • views.html.article - I really have to tell the ArticleController to use HTML and to use the article view?
  • Cached - why is this in the controller? This should be a separate concern.

I realize that some of this might be because of Scala's type system, but it just feels like we've taken the same boilerplate from XML or Annotations and made it code and somehow that makes it OK.

I hated repeating myself and telling the framework what it should've already known in XML, XDoclet, and annotations. I don't like it any better in code.

@danielhopkins
Copy link

Doesn't it seem odd to have public methods in a controller that aren't taking a request and returning a response? Thus, why do I need to put Action on them? They should be assumed to be actions, because what > else would they be?

I think the intent is that controllers are less about the object and more about the functions. The router passes requests to a function that returns a response vs. setting up an object, initializing a bunch of state and then calling one of it's methods. In other words, the object itself is probably the biggest boilerplate. Play would be just as happy if the functions could just exist without having to arbitrarily name spaced, but without the top level object the JVM would be unhappy.

@davetron5000
Copy link
Author

Isn't an object just a container for functions? Why do I need indirection between a URL and "what to do"? I find it very confusing in Java web apps where the urls and controller names/methods don't match up, especially when there is no real reason they should not. a GET to a route like /people should, without explicit configuration, be serviced by, for example, PeopleController.get.

As to "initializing a bunch of state", this can actually make the underlying API more fluent, and git rid of all the implicit cruft. At a simplistic level:

class PeopleController(request:HttpRequest, val response:HttpResponse) {
  def get = { 
    request // available without having to ask for it with code
    response // again, available without having to ask permission in code
  }
}

// framework code:

val controller = PeopleController.new(request,response)
controller.get
doSomethingWith(controller.response)

So, I guess my beef is with API design - I'm in a controller - it's a special type of class/object, so there are huge advantages to making things easier. If the purpose of the class is to service an HTTP request, why not make it as simple as possible for me to access the raw request, headers, params, etc?

@pk11
Copy link

pk11 commented Nov 27, 2012

Just to give my 2c.

Doesn't it seem odd to have public methods in a controller that aren't taking a request and returning a response?

In Play controllers are just plain old scala objects (potentially built from a bunch of mixins) - extending from play.api.mvc.Controller just gives you a few helpers, other than that, there is no special meaning attached to the Controller type. A controller method is special only in that sense that it needs to produce an Action at some point. This gives the developer full control over how he/she would like to organize their codebase. Personally, I tend to keep only real "Action producer" methods in my controller (just the way you suggested) but it's not enforced.

Lack of view conventions
2) We do have conventions when they make sense but we also try to keep controllers as "framework-free" as possible. This design decision allows developers to add their own conventions very easily, as well as plugin any view technology they prefer

Cached - I dunno; caching should be a cross-cutting concern, shouldn't it?
Indeed. I would have implemented this as function composition

implicit request
This is necessary only if within your action block an underlying method would take it. It's usually a good idea in scala to avoid hiding implicit arguments (i.e. "be explicit with your implicit"). That said, in your example above, I would question if the request needs to be implicit at all or whether passing it twice top-down (once for lookup, once for renderArticle) is the right way to go.

Why is this here? Several of this apps controllers have this exact structure. Shouldn't this concern be separated somehow?
I totally agree with this point. It looks to me the asynchronous rendering+lookup behavior should be somehow encapsulated

Anyway, my main point is that play provides a generic toolkit and we tried to strike a good balance between providing flexibility and avoiding unnecessary sit-ups. I feel like some of your concerns are specific to this codebase (and I actually agree with most of your points) and some of them are related to the fact that it seems that you tend to prefer the implicitness of Rails (which is fine but that's a different strategy than what we try to accomplish with Play).

HTH,
Peter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment