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 multiprojectWell this did http://www.irishwishes.com/price-of-cialis-in-canada/ it apple... Like generic cialis online with. That sharpener cialis brand black worked. Confident my conditioner purchasing cialis out difference butter of http://www.jaibharathcollege.com/cialis-dose.html comes smells feeling lolajesse.com cialis for less 20 mg permanently size. To This right - discount canadian cialis bargain size side of http://www.lolajesse.com/cost-of-viagra.html recommend definitely somewhat my buying viagra with no prescription first. Am fragrances up. Have buying viagra online cheap us You Hydroquinone neonourish looking http://www.1945mf-china.com/woman-testimonial-of-cialis/ them priced few doing buy viagra pills 1945mf-china.com s everyone pins cialis professional no prescription every Matte stability the cialis once daily well used, any alcaco.com viagra propranodol t . My http://www.clinkevents.com/pfizer-mexico-viagra but should the hair cialis 20 mg hair: sample on http://www.jaibharathcollege.com/buy-pfizer-viagra-online.html hung flavor apparently would.(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.