Recently I spent some time with Howard Lewis Ship, creator of the Apache Tapestry web framework. Howard is a technical rock star so it was really fun to sit down with him and hack on some code. Our goal was to make it easy for people to run their Tapestry apps on the cloud with Heroku. You can run anything on Heroku so there are a variety of ways to run Tapestry apps on Heroku. We wanted to put together something that helps Tapestry users run their apps in the most optimal way. What we came up with is available in Howard’s tapx-heroku package on GitHub. Lets walk through what it does.
The JettyMain class provides a simple way to start a Java web app using an embedded Jetty server. This “Containerless” method has these advantages over the traditional container deployment model:
- The HTTP handling library (Jetty in this case) is a dependency of my application. This alleviates pain caused by differences in developer and production deployment environments.
- My app is just a plain Java library so it can be packaged as a regular JAR file and used as a dependency itself.
- The application starts up super fast.
- I have a way to setup the application (like the session storage provider) in code instead of having to do it in XML.
- Development cycles are faster as a result of lightweight packaging and deployment. With IntelliJ or JRebel class hot-swapping can make those cycles even shorter. (This is also an out-of-the-box feature with Tapestry.)
To use JettyMain with your Tapestry app, we just need to update the Maven build (pom.xml) file. You can do this with an existing app or start from scratch using the Tapestry Maven Archetype. Here is a simple pom.xml file:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> <name>herokuapp</name> <artifactId>herokuapp</artifactId> <packaging>jar</packaging> <repositories> <repository> <id>howardlewisship</id> <url>http://howardlewisship.com/snapshot-repository</url> </repository> </repositories> <dependencies> <dependency> <groupId>org.apache.tapestry</groupId> <artifactId>tapestry-core</artifactId> <version>5.3.2</version> </dependency> <dependency> <groupId>com.howardlewisship</groupId> <artifactId>tapx-heroku</artifactId> <version>1.2-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.4</version> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals><goal>copy-dependencies</goal></goals> </execution> </executions> </plugin> </plugins> </build> </project>
This specifies Tapestry and the tapx-heroku libraries as dependencies. The tapx-heroku library will automatically pull in the Jetty dependencies. The maven-dependency-plugin will copy the runtime dependencies from the local Maven cache to the target/dependency directory so that we can easily setup the application’s runtime classpath.
To build the app locally just run:
To launch the application run:
java -cp target/dependency/*:target/classes com.howardlewisship.tapx.heroku.JettyMain
That’s it! Tapestry, tapx-heroku (JettyMain), Jetty, your application classes, and the rest of the required dependencies are all loaded into the JVM and the web server is started. Super simple stuff!
The JettyMain app has a few optional command line arguments for overriding the defaults. You can see those by running:
java -cp target/dependency/*:target/classes com.howardlewisship.tapx.heroku.JettyMain -help
If you want to try out a simple app that already has this setup then I have one on GitHub that you can copy locally by doing a git clone:
git clone https://github.com/jamesward/hellotapestry.git
Now lets run this on the cloud with Heroku. To follow along you need to install the Heroku toolbelt and signup for a Heroku.com account. Don’t worry, you won’t need to enter a credit card to give this a try because Heroku gives you 750 free dyno hours per application, per month. (Wondering what a “dyno” is? Check out: How Heroku Works)
We need to tell Heroku what process on the system to run in order to start up the web application. To do this we need a file named Procfile (with a capital ‘P’) in the project’s root directory. The contents of that file should be:
web: java -cp target/dependency/*:target/classes com.howardlewisship.tapx.heroku.JettyMain
In this case we will upload the source up to Heroku where it will run the Maven build. This is the standard Heroku deployment mechanism because it makes the deployment process super fast and makes sure that there are no discrepancies between the different environments. The dependencies are downloaded on the cloud (i.e. very quickly) instead of doing the typical lengthy package and upload process. But you can also upload binary assets if you prefer. Heroku uses git for file upload so if you don’t already have the project files in a git repo then you need to create the repo, add the files, and commit them:
git init git add src pom.xml Procfile git commit -m init
Now we can provision an application on Heroku by logging in from the Heroku CLI and creating an application on the “cedar” stack:
heroku login heroku create -s cedar
The application can now be uploaded using git:
git push heroku master
When the upload has finished Heroku will run the Maven build, deploy the compiled application onto a dyno, and start the web process defined in the Procfile. Test out the application in your browser by running:
Great! You now have a Tapestry app running on the cloud! But there is one other cool thing the JettyMain does for us. Java web applications often use in-memory session state which does not work well with the server affinity model that is necessary for scalable cloud deployments. Out-of-the-box Tapestry uses the session state very minimally but it’s possible that your application uses it more heavily. Heroku dynos are intended to be stateless so they can be managed, scaled, and configured without disrupting active users. This means that the session state needs to be in an external store. Luckily Jetty makes it easy to transparently move session state to an external MongoDB system (see my Using MongoDB for a Java Web App’s HttpSession post for more details).
The JettyMain app has this functionality built in as long as you either set the mongo-url application parameter or have set either a MONGOHQ_URL or MONGOLAB_URL environment variable. On Heroku it’s very easy to create a MongoDB system for your application to use. The Heroku add-ons provide numerous Cloud Services to Heroku apps, including two MongoDB providers: MongoLab and MongoHQ. Both have free tiers, so pick one and then add it by running either of these commands:
heroku addons:add mongolab
heroku addons:add mongohq
This will automatically provision the MongoDB system, set the connection information in the expected environment variable, and then restart your dyno(s) so that they pickup the new configuration. The JettyMain will now switch Jetty’s session storage provider to the MongoDB system instead of using in-memory storage. Cool stuff!
Let me know if you have any questions and thanks Howard for putting this together!