Multi-modular development with Play 2.1

Published on 26 March 2013

Play 2.1 makes it possible to write completely modular applications, and on top of this, to develop each module separately. I’ve been waiting for this for some time now, and even implemented a custom router based on plugins while waiting for the framework to support modular routing. Now that the wait is over, let’s see how to take advantage of this feature.

The Play documentation describes how to set-up a multi-modular project via SBT sub-projects. What is important to understand here is that:

Let’s get started with creating a new project:

[bash]
[email protected] ~/workspace » play new multi-module-example 255 ?
_ _
_ __ | | __ _ _ _| |
| ‘_ \| |/ _’ | || |_|
| __/|_|\____|\__ (_)
|_| |__/

play! 2.1.0 (using Java 1.7.0_15 and Scala 2.10.0), http://www.playframework.org

The new application will be created in /Users/manu/workspace/multi-module-example

What is the application name? [multi-module-example]
>

Which template do you want to use for this new application?

1 – Create a simple Scala application
2 – Create a simple Java application

> 1
OK, application multi-module-example is created.

Have fun!
[/bash]

Now that we have created a parent project, let’s right away create a first module:

[bash]
[email protected] ~/workspace » cd multi-module-example
[email protected] ~/workspace/multi-module-example » mkdir modules
[email protected] ~/workspace/multi-module-example » cd modules
[email protected] ~/workspace/multi-module-example/modules » play new module1
_ _
_ __ | | __ _ _ _| |
| ‘_ \| |/ _’ | || |_|
| __/|_|\____|\__ (_)
|_| |__/

play! 2.1.0 (using Java 1.7.0_15 and Scala 2.10.0), http://www.playframework.org

The new application will be created in /Users/manu/workspace/multi-module-example/modules/module1

What is the application name? [module1]
>

Which template do you want to use for this new application?

1 – Create a simple Scala application
2 – Create a simple Java application

> 1
OK, application module1 is created.

Have fun!
[/bash]

Now that we have the basic structure in place, let’s adjust the main’s project Build.sbt. At this point, we just want to make sure that our sub-module gets built, tested and packaged together with the main project. That is why we need to add the aggregate instruction to the main project. The projet/Build.scala build file should look like this:

[scala]
import sbt._
import Keys._
import play.Project._

object ApplicationBuild extends Build {

val appName = "multi-module-example"
val appVersion = "1.0-SNAPSHOT"

val appDependencies = Seq(
// Add your project dependencies here,
jdbc,
anorm
)

val module1 = play.Project("module1", path = file("modules/module1"))

val main = play.Project(appName, appVersion, appDependencies).settings(
// Add your own project settings here
).dependsOn(
module1
).aggregate(
module1
)

}
[/scala]

Note that we do not any longer need the Build.scala of the module, i.e. the directory modules/module1/project can be deleted.

Now, what we need for our module to work properly, is to:

First, let’s rename the modules/module1/conf/routes file to modules/module1/conf/module1.routes and adjust its content:

[plain]
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET /module1 controllers.module1.Application.index

# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.module1.Assets.at(path="/public", file)
[/plain]

Note how the generated Application controller is now moved to the module1 package (it needs to be adjusted in the modules/module1/app/controllers/Application.scala file as well), and the Assets controller is also part of the module1 package. Each sub-project needs its own Assets controller, if it should provide assets.

The Assets file looks like this:

[scala]
package controllers.module1
object Assets extends controllers.AssetsBuilder
[/scala]

Now, let’s plug-in the new router in the main’s project router, in the conf/routes file:

[scala]
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET / controllers.Application.index

-> / module1.Routes

# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
[/scala]

We can check that this works by accessing an undefined route:

all-routes

Now that everything is in place, there is just one last thing to do in order to make the development a pleasurable experience. Indeed, it is possible to develop each module separately, and treat it as a separate application, by switching to this project in the SBT console:

[bash]
[multi-module-example] $ projects
[info] In file:/Users/manu/workspace/multi-module-example/
[info] module1
[info] * multi-module-example
[multi-module-example] $ project module1
[info] Set current project to module1 (in build file:/Users/manu/workspace/multi-module-example/)
[module1] $ run

— (Running the application from SBT, auto-reloading is enabled) —

[info] play – Listening for HTTP on /0.0.0.0:9000

(Server started, use Ctrl+D to stop and go back to the console…)
[/bash]

However, this won’t work right away:

no-router

As we now switched to the module project, effectively making it the currently active Play project, the default application configuration kicks in. By default, the router is defined in the conf/routes file.

Luckily, there is a way to change this in the application.conf:

[plain]
# Router
# ~~~~~
# Define the Router object to use for this application.
# This router will be looked up first when the application is starting up,
# so make sure this is the entry point.
# Furthermore, it’s assumed your route file is named properly.
# So for an application router like my.application.Router,
# you may need to define a router file conf/my.application.routes.
# Default to Routes in the root package (and conf/routes)
# application.router=my.application.Routes
[/plain]

Let’s adjust our module’s application.conf to do just this, and to also inherit from the parent’s configuration:

[plain]
include "../../../conf/application.conf"

application.router=module1.Routes
[/plain]

And that’s pretty much it, we can now work independently on our new module:

module1-route


Liked this post? Subscribe to the mailing list to get regular updates on similar topics.

6 Comments

  1. Hi,

    great tutorial – thank you!!

    I’ve just got one question:
    ” The Assets file looks like this:
    1 package controllers.module1
    2 object Assets extends controllers.AssetsBuilder ”

    Is this just an Assets.scala file in the controllers directory of each module?

    Also, what would you do with the routes file if you’d have multiple modules?

    Thanks!
    David

    • The Assets file is a file in the “controllers.module1” directory of each module. I mean, you could also have the compilation unit in the controllers package, but that way might be cleaner (and if you build any controllers, you’ll place them in there probably).
      If you have multiple modules, just include them each in the parent routes file – nothing special happens there

  2. And is it possible to access the main-applications “public” folder (for css/js/img etc) from a module??

    Thanks again

    David

    • As far as I know, that’s not possible – if you want to share resources across sub-modules, I’d go for making a “common” module that each module depends upon, using the dependsOn directive of SBT, and put those shared resources in there. That module needs to have its own Assets compiler, then.

  3. Hi Manuel, I need to convert and modularize an application, but slightly different that you show up. My need is to create an application based on plugin, where my Play controllers, views and models are inside a JAR file. I put this JAR in some place and Play must detect an load into application without any configuration, is it possible ?

    Regards.

    • Hi,

      yes this should be possible. If you want it to be fully dynamic though you’ll have to add a bit of logic to the Global object regarding route resolution because you’ll want your routes to be dynamic. With the built-in router, you have to explicitly add the routes of any sub-module in the main routes file by hand, so this is not what you want here. See the documentation here for the entry point: http://www.playframework.com/documentation/2.2.x/ScalaInterceptors

Leave a Reply