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.
sub-projects are a feature of SBT, and next to having a central Build.scala file, it is possible to add configuration to individual sub-projects via build.sbt files (in those sub-project’s root directory)
the proposed folder structure (i.e. putting the modules in a modules/ directory) is optional (but a good convention nonetheless)
each sub-project is a play.Project, so routes and messages files in conf/ will be recognized
[email protected] ~/workspace » play new multi-module-example
_ _
\_ __ ||\__ _ _ _|||'_ \| |/ \_'||||\_||\_\_/|\_|\\\_\_\_\_|\\_\_(_)|\_||\__/
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!
Now that we have created a parent project, let’s right away create a first module:
[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!
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:
importsbt._importKeys._importplay.Project._objectApplicationBuildextendsBuild{valappName="multi-module-example"valappVersion="1.0-SNAPSHOT"valappDependencies=Seq(// Add your project dependencies here,
jdbc,anorm)valmodule1=play.Project("module1",path=file("modules/module1"))valmain=play.Project(appName,appVersion,appDependencies).settings(// Add your own project settings here
).dependsOn(module1).aggregate(module1)}
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:
Set-up the routing correctly
Move the controller to the right package
Wire-up the routes of the sub-module into the parent’s router
First, let’s rename the modules/module1/conf/routes file to modules/module1/conf/module1.routes and adjust its content:
1
2
3
4
5
6
7
8
9
10
# 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)
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.
Now, let’s plug-in the new router in the main’s project router, in the conf/routes file:
1
2
3
4
5
6
7
8
9
10
11
12
# 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)
We can check that this works by accessing an undefined route:
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:
1
2
3
4
5
6
7
8
9
10
11
12
[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...)
However, this won’t work right away:
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:
1
2
3
4
5
6
7
8
9
10
11
# 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
Let’s adjust our module’s application.conf to do just this, and to also inherit from the parent’s configuration:
1
2
3
include "../../../conf/application.conf"
application.router=module1.Routes
And that’s pretty much it, we can now work independently on our new module: