Play Framework projects can build from several Play / sbt projects. A common use case is to keep code modular and composable. The official documentation shows how to combine Play projects, but does not specifically show how to create separate deployment packages that each are composed of different services.
At FortyTwo, we use a single repository for our backend code. On deployment, however, our packages only contain the code they need. We had the following objectives:
- Development should be simple.
play runshould be enough to run all services at the same time.
- Common dependencies and modules should be easily shared.
- We should be able to compile / test / run each service separately in development and production.
- Deployment packages should contain only the classes that the service needs.
- Routes should be split by service.
Play and SBT documentation show how to create a multi-project
Build.scala file. When you run Play / sbt, you’ll load one of the projects by default. It makes whatever project variable name comes first alphebetically the default — so you’ll likely want to name your top-level project
aaaMain or similar.
In the sbt console, you can see all of the projects with
projects. To change the project, use
project projectName. From there, you can compile / test independently.
We built a sample Play application to show how to do these ( Github repo).
multiproject is the default top level project to be used in development, and uses two services.
serviceA is like a user-facing web service. Its routes look like:
GET / controllers.serviceA.Application.home() GET /serviceA controllers.serviceA.Application.main() GET /serviceA/:name controllers.serviceA.Application.greet(name: String)
serviceB’s routes look like:
GET /serviceB controllers.serviceB.Application.main() GET /serviceB/lottery controllers.serviceB.Application.lottery()
multiproject, when you run
play run, both
serviceB will run together, so you can work on either service and they can communicate to each other. multiproject is configured with
Build.scala such that
test run across all sub-projects as well.
play.Project("multiproject", appVersion, commonDependencies ++ serviceADependencies ++ serviceBDependencies).settings().dependsOn(common, serviceA, serviceB).aggregate(common, serviceA, serviceB)
andrew$ play [info] Set current project to multiproject (in build file:/Users/andrew/Documents/workspace/multiproject/) _ _ _ __ | | __ _ _ _| | | '_ | |/ _' | || |_| | __/|_|____|__ (_) |_| |__/ play! 2.1.1 (using Java 1.6.0_45 and Scala 2.10.0), http://www.playframework.org > Type "help play" or "license" for more information. > Type "exit" or use Ctrl+D to leave this console. [multiproject] $ projects [info] In file:/Users/andrew/Documents/workspace/mp2/ [info] common [info] * multiproject [info] serviceA [info] serviceB [multiproject] $ run --- (Running the application from SBT, auto-reloading is enabled) --- [info] play - Listening for HTTP on /0:0:0:0:0:0:0:0%0:9000 (Server started, use Ctrl+D to stop and go back to the console...)
Additionally, you can also compile / test / run each service (or even the common package) independently.
[multiproject] $ project serviceA [info] Set current project to serviceA (in build file:/Users/andrew/Documents/workspace/multiproject/) [serviceA] $ test [info] ApplicationSpec [info] [info] CommonApplication should [info] + send 404 on a bad request [info] + render the status page ... snip (sbt runs tests for just serviceA and common) ... [serviceA] $ run --- (Running the application from SBT, auto-reloading is enabled) --- [info] play - Listening for HTTP on /0:0:0:0:0:0:0:0%0:9000
Notice how serviceA only needed to compile and test
common, rather than everything. This is especially cool for deployments. While you get all the benefits of a unified codebase, you can deploy completely separate packages for each service.
[multiproject] $ dist ... snip ... Your application is ready in /Users/andrew/Documents/workspace/multiproject/modules/common/dist/common-master-dc4e606-20130617-151706.zip Your application is ready in /Users/andrew/Documents/workspace/multiproject/dist/multiproject-master-dc4e606-20130617-151706.zip Your application is ready in /Users/andrew/Documents/workspace/multiproject/modules/serviceB/dist/serviceb-master-dc4e606-20130617-151706.zip Your application is ready in /Users/andrew/Documents/workspace/multiproject/modules/serviceA/dist/servicea-master-dc4e606-20130617-151706.zip
Since each of these are self contained, your deployment manager can push these distributions to their respective application servers.
As a note, since each deployment will not have all of the classes, the default Play reverse router will not be able to determine URLs cross-service. You will likely want to write your own reverse router in a common module.
Clone the separate multi-project deployment packages git repository on Github to play with this.
We wrote this post while working on Kifi — tools that help people outsmart information overload together. Learn more.