Action composition in Play Framework

In Play Framework controllers consist of methods that create Action objects to handle the incoming requests. For example in the HelloController below the helloWorld and hello methods create actions that return a “hello” message to the client. In this case, the action objects are constructed using the apply factory method of the Action singleton object.

import play.api.mvc.{Controller, Action}

object HelloController extends Controller {

  def helloWorld = Action {
    Ok("Hello World")
  }

  def hello(name: String) = Action {
    Ok(s"Hello $name")
  }
}

Why we need action composition

Let’s suppose that we want to extend the above actions by adding some logging to assist us when debugging. One way to do this is by adding the log statements in each action definition.

import play.api.Logger
import play.api.mvc._

object HelloController extends Controller {

  def helloWorld = Action { request =>
    Logger.debug(s"HTTP request: ${request.method} ${request.uri}")
    Ok("Hello World")
  }

  def hello(name: String) = Action { request =>
    Logger.debug(s"HTTP request: ${request.method} ${request.uri}")
    Ok(s"Hello $name")
  }
}

As you can see from the above code this is a naive approach to implement such feature since it introduces code duplication. Things like logging that are not related to the main concern of a component and are usually scattered in multiple components are very common in programming and are called cross-cutting concerns. In Play Framework one way to separate cross-cutting concerns from the code of actions is using action composition (the other way is filters but we will not cover them in this article).

To extract logging from the helloWorld and hello methods using action composition what we have to do is create another action that does the logging (the cross-cutting concern) and can be composed with other actions that implement the main concern (in our example the actions of helloWorld and hello) returning a new action that contains the logic for the cross-cutting and the main concern. This way the logic for logging is in one place and can easily change.

In Play Framework Actions are usually created using factory objects that implement the ActionFunction trait. The PlayFramework API contains various traits that extend ActionFunction and allow us to customize the created actions in different ways. Those traits are ActionBuilder, ActionFilter, ActionRefiner and ActionTransformer. From those the most generic and the most helpful is ActionBuilder (in fact the Action singleton object, which is the default ActionFunction is an ActionBuilder).

Because the action of the cross-cutting concern must return another action we can think of it like an action factory and implement it using an appropriate ActionFunction provided by the Play Framework. Here we will use the ActionBuilder to implement it.

The ActionBuilder

ActionBuilder is usually our default choice because it provides several factory methods that make action definition easier. To implement ActionBuilder all we have to do is implement the invokeBlock method. This method takes the current request and a block of code as arguments and will get invoked when the Action returned from the ActionBuilder executes.

For example the code Action { Ok } is a call to apply(block: => Result) method of ActionBuilder that passes the { Ok } block of code as an argument and returns an Action[AnyContent]. When this Action executes it will invoke the invokeBlock method of the ActionBuilder with the current request and the { Ok } block as arguments.

With the above information we can implement the LoggedAction as follows:

import scala.concurrent.Future
import play.api.Logger
import play.api.mvc._

object LoggedAction extends ActionBuilder[Request] {

  def invokeBlock[A](
      request: Request[A],
      block: Request[A] => Future[Result]): Future[Result] = {
    Logger.debug(s"HTTP request: ${request.method} ${request.uri}")
    block(request)
  }
}

and then use it in our controller in the same way that we use the play.api.mvc.Action singleton object since they are both ActionBuilder implementations.

object HelloController extends Controller {

  def helloWorld = LoggedAction {
    Ok("Hello World")
  }

  def hello(name: String) = LoggedAction {
    Ok(s"Hello $name")
  }
}

Extending the Request object

When we implemented the LoggedAction all we had to do is add the logging statement before the invocation of the action’s code block. There are cases when this is not enough and we also want add custom information to the request before we pass it to the code block of the action. An example where we would like to do this is user authentication. In user authentication we usually want to verify that the user making the request is an authenticated user and if this is the case add a user-like object to the request so that we don’t have to load the user again in the action’s code.

To add custom information to a request Play Framework provides the WrappedRequest class. As its name implies the WrappedRequest[A] class is a Request[A] that wraps another Request[A]. To define a custom request that also holds the currently logged in user we can use the following code:

import play.api.mvc.{Request, WrappedRequest}
import models.User

class AuthenticatedRequest[A](val user: User, val request: Request[A])
  extends WrappedRequest[A](request)

Then we can use ActionBuilder to define an AuthenticatedAction that checks if the user is authenticated and invokes block with an AuthenitcatedRequest that wraps the current request and has the authenticated user. AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] and not ActionBuilder[Request] because we want the block to have the type AuthenticatedRequest[A] => Future[Result] so that the user property is accessible when we define actions.

import scala.concurrent.Future
import play.api.mvc._
import models.User

object AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] {

  def invokeBlock[A](
      request: Request[A],
      block: AuthenticatedRequest[A] => Future[Result]): Future[Result] = {
    request.session.get("username")
      .flatMap(User.findByUsername(_))
      .map(user => block(new AuthenticatedRequest(user, request)))
      .getOrElse(Future.successful(Results.Forbidden))
  }
}

We can easily use our AuthenticatedAction in our controllers since it is an ActionBuilder. The request parameter of the code block is of type AuthenticatedRequest and so the code of the action can access the user property.

object HelloController extends Controller {

  def helloUser = AuthenticatedAction { request =>
    Ok(s"Hello ${request.user.firstName}")
  }
}

The ActionRefiner

Because changing the incoming request (to add custom information or to modify it in any way) is very common Play Framework provides three ActionFunction extensions to do it more easily. The main ActionFunction to do this is ActionRefiner and the other two are ActionTransformer and ActionFilter.

An ActionRefiner[R, P] will either return immediately a result of type R or call its action block with a request of type P. To implement an action refiner all we have to do is implement its refine method. For example we can implement AuthenitcatedAction with an ActionRefiner as follows:

object AuthenticatedAction
  extends ActionBuilder[AuthenticatedRequest]
  with ActionRefiner[Request, AuthenticatedRequest] {

  def refine[T](request: Request[T]): Future[Either[Result, AuthenticatedRequest[T]]] = {
    val result = request.session.get("username")
      .flatMap(User.findByUsername(_))
      .map(user => new AuthenticatedRequest[T](user, request))
      .toRight(left = Results.Forbidden)
    Future.successful(result)
  }
}

The refine method returns the result Forbidden in the case where the user is not authenticated or in the case where the user is authenticated it transforms the incoming request to an AuthenticatedRequest. The transformed request will be passed as an argument in the action’s code block.

In the above code example you probably see something that seems odd. The AuthenticatedAction extends both ActionBuilder and ActionRefiner. This is because ActionRefiner does not provide any convenient factory methods to create actions. We can create actions using a plain ActionRefiner but it requires some boilerplate code. ActionRefiner provides us with a useful workflow (to wrap the incoming request under some conditions) and an extension point (the refine method) to lets us specify the specifics. On the other hand ActionBuilder provides no workflow but gives us a handful of useful methods to define actions. The two can be mixed as in the above example so we end up writing less code.

The ActionTransformer

ActionTransformer is an extension to ActionRefiner that always transforms the incoming request. To implement a transformer we implement its transform method returning the transformed request that will be passed in the action’s code block.

An example of an ActionTransformer might be in a chat system where we want for each request to find a chat room and if one does not exist then create it.

object ChatAction
  extends ActionBuilder[ChatRequest]
  with ActionTransformer[Request, ChatRequest] {

  def transform[A](request: Request[A]): Future[ChatRequest[A]] = {
    Future.successful(new ChatRequest(getOrCreateChatRoom(), request))
  }

  private def getOrCreateChatRoom(): ChatRoom = ???
}

The ActionFilter

ActionFilter is the other extension of ActionRefiner and allows us to filter certain requests preventing the action from being executed. To do this we have to implement the filter method and return a Some[Result].

For example if we wanted to check and make sure that the request is using SSL before calling the action to handle the request we could do it with an action filter as follows:

object CheckSSLAction
  extends ActionBuilder[Request]
  with ActionFilter[Request] {

  def filter[A](request: Request[A]): Future[Option[Result]] = {
    val result = request.headers.get("X-Forwarded-Proto") match {
      case Some(proto) if proto == "https" => None
      case _ => Some(Results.Forbidden)
    }
    Future.successful(result)
  }
}

The andThen combinator

So far we have seen how easy it is to define various ActionFunctions that separate cross-cutting concerns from the code of controller actions. But what if we have requirements to support multiple cross-cutting concerns in a single action. For example what if an action needs to execute only when the request is over SSL and when the user is authenticated. Is there a way to combine the action functions that we have already created instead of defining a new one?

Fortunately the ActionFunction trait has a very useful method called andThen that allows us to combine two ActionFunction. To implement the above example we can use the following code:

object Actions {
  val SSLAndAuthAction = CheckSSLAction andThen AuthenticatedAction
}

Summary

  • Use ActionBuilder when you want to add before/after logic in your actions.
  • Use ActionRefiner when you want to add custom information to a request under some conditions.
  • Use ActionTransformer when you want to always add custom information to a request.
  • Use ActionFilter when you want to filter requests under some condition and immediately return a result.
  • Always mix ActionRefiner, ActionTransformer and ActionFilter to ActionBuilder so you can use factory methods to easily construct your actions.
  • Use the andThen combinator to compose multiple ActionFunctions together.