Separate multi-project deployment packages in Play! Framework
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 run should 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()
In multiproject, when you run play run, both serviceA and 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 compile and test run across all sub-projects as well.
play.Project("multiproject", appVersion, commonDependencies ++ serviceADependencies ++ serviceBDependencies).settings().dependsOn(common, serviceA, serviceB).aggregate(common, serviceA, serviceB)
Additionally, you can also compile / test / run each service (or even the common package) independently.
Notice how serviceA only needed to compile and test serviceA and 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.
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 — Connecting people with knowledge. Learn more.
Originally published at eng.kifi.com on June 18, 2013.