Running Play Framework + Scala Apps on Heroku

Building Play Framework apps with Scala is all the rage right now. And for good reason… It’s never been easier to build and deploy JVM-based web apps! Lets walk through how to build a Play app with Scala and then deploy it on the cloud with Heroku.

Step 1) Install the Play Framework (make sure you have at least version 1.2.3)

Step 2) Install the Play Scala module:

play install scala

Step 3) Create a new Play app with Scala support:

play new playwithscala --with scala

Step 4) Start the app:

cd playwithscala
play run

Step 5) Open the app in your browser:
http://localhost:9000

That was easy! Lets spice this up a bit before we deploy it on Heroku by adding a custom model, view, and controller.

Step 1) Create a new app/models/Widget.scala file containing:

package models
 
case class Widget(id: Int, name: String)

Step 2) Create a new app/views/Widget/list.scala.html file containing:

@(widgets: Vector[models.Widget])
 
<!DOCTYPE html>
<html>
    <body>
        @widgets.map { widget => 
            Widget @widget.id = @widget.name</br>
        }
    </body>
</html>

Step 3) Create a new app/controllers/WidgetController.scala file containing:

package controllers
 
import play._
import play.mvc._
 
object WidgetController extends Controller {
 
    import views.Widget._
    import models.Widget
 
    def list = {
        val widget1 = Widget(1, "The first Widget")
        val widget2 = Widget(2, "A really special Widget")
        val widget3 = Widget(3, "Just another Widget")
        html.list(Vector(widget1, widget2, widget3))
    }
 
}

Step 4) Test out the new code by going to:
http://localhost:9000/WidgetController/list

It works! And we didn’t even need to reload the server! But lets clean up that URL a bit. Edit the conf/routes file and change “Application.index” to “WidgetController.list”:

GET     /                                       WidgetController.list

Now load the new URL:
http://localhost:9000/

That was easy but now we want to show our friends. So lets deploy it on Heroku.

Step 1) Install the Heroku command line client on Linux, Mac, or Windows

Step 2) Login to Heroku from the command line:

heroku auth:login

Step 3) Heroku uses git for application upload, so create a .gitignore file containing:

/modules
/tmp

Step 4) Create a git repo, add the files to it, and commit them:

git init
git add .
git commit -m init

Step 5) Create the new application on Heroku:

heroku create -s cedar

This provisions a new application on Heroku and assigns a random name / URL to the app.

Step 6) Deploy the application:

git push heroku master

The application will now be assembled and deployed on Heroku.

Step 7) Open the application in the browser:

heroku open

Tada! Your Play + Scala app is now running on the cloud!

At JavaOne I showed this to Bill Venners (creator of ScalaTest). He then moved the scalatest.org website (a Play + Scala app) to Heroku! Cool stuff!

Let me know if you have any questions.

Heroku Java User Group Tour Part 1: Los Angeles and Salt Lake City

This week I’m starting a Java User Group tour where I’ll be travelling to JUGs around the US (or maybe world). On the tour I’ll be giving a talk about Running Java, Play! and Scala Apps on the Cloud. Here is the description:

Heroku is a Polyglot Cloud Application Platform that makes it easy to deploy Java, Play! and Scala apps on the cloud. Deployment is as simple as doing a “git push”. This session will teach you how to instantly deploy and scale Java, Play! and Scala apps on Heroku.

I’m still scheduling JUGs but here are the first two I’ll be doing:

There will be more to come and if you’d like this talk at your local Java User Group, let your leader know and have them email me: jw <at> heroku <dot> com

Hopefully see you at your local JUG!

Sending Play Framework File Uploads to Amazon S3

UPDATE: I’ve released a S3 Play Module based on this project.

A couple of questions [1, 2] on StackOverflow.com led me to look into how we can send file uploads in a Play Framework application to Amazon S3 instead of the local disk. For applications running on Heroku this is especially important because the local disk is not persistent. Persistent disk storage makes it hard to scale apps. Instead of using the file system, it’s better to use an external service which is independent of the web tier.

While at JavaZone I sat down with Peter Hilton and Nicolas Leroux to come up with a way to handle this. It only took us 30 minutes to get something working – start to finish – including setup time. This is what is so compelling about Play Framework. I’ve built many Java web apps and it always seems like I spend too much time setting up builds, IDEs, and plumbing. With Play we were setup and working on the actual app in less than a minute. After getting everything working locally it took another minute to actually run it on the cloud with Heroku. The combination of Play Framework and Heroku is a developer’s dream for fast-paced development and deployment.

All of the code for the sample application is on github:
https://github.com/jamesward/plays3upload

The basics of what we did was this:

public static void doUpload(String comment, File attachment)
{
    AWSCredentials awsCredentials = new BasicAWSCredentials(System.getenv("AWS_ACCESS_KEY"), System.getenv("AWS_SECRET_KEY"));
    AmazonS3 s3Client = new AmazonS3Client(awsCredentials);
    s3Client.createBucket(BUCKET_NAME);
    String s3Key = UUID.randomUUID().toString();
    s3Client.putObject(BUCKET_NAME, s3Key, attachment);
    Document doc = new Document(comment, s3Key, attachment.getName());
    doc.save();
    listUploads();
}

This uses a JPA Entity to persist the metadata about the file upload (for some reason we named it ‘Document’) and a reference to the file’s key in S3. But there was a sexier way, so my co-worker Tim Kral added a new S3Blob type that could be used directly in the JPA Entity. Tim also cleaned up the configuration to make it more Play Framework friendly. So lets walk through the entire app so you can see the pieces.

The app/models/Document.java JPA Entity has three fields – the file being of type S3Blob:

package models;
 
import javax.persistence.Entity;
 
import play.db.jpa.Model;
import s3.storage.S3Blob;
 
@Entity
public class Document extends Model
{
    public String fileName;
    public S3Blob file;
    public String comment;
}

The S3Blob is now doing all of the work to talk to the Amazon S3 APIs to persist and fetch the actual file.

Configuration of S3 is done by adding a plugin to the conf/play.plugins file:

0: s3.storage.S3Plugin

The S3Plugin handles reading the AWS credentials from the conf/application.conf file, setting up the S3Client, and creating the S3 Bucket – if necessary.

In the conf/application.conf file, environment variables are mapped to the configuration parameters in the Play application:

aws.access.key=${AWS_ACCESS_KEY}
aws.secret.key=${AWS_SECRET_KEY}
s3.bucket=${S3_BUCKET}

The values could be entered into the conf file directly but I used environment variables so they would be easier to change when running on Heroku.

The Amazon AWS API must be added to the conf/dependencies.yml file:

require:
    - play
    - com.amazonaws -> aws-java-sdk 1.2.7

The sample application has a new controller in app/controllers/Files.java that can display the upload form, handle the file upload, display the list of uploads, and handle the file download:

package controllers;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.List;
 
import models.Document;
import play.libs.MimeTypes;
import play.mvc.Controller;
import s3.storage.S3Blob;
 
public class Files extends Controller
{
 
  public static void uploadForm()
  {
    render();
  }
 
  public static void doUpload(File file, String comment) throws FileNotFoundException
  {
    final Document doc = new Document();
    doc.fileName = file.getName();
    doc.comment = comment;
    doc.file = new S3Blob();
    doc.file.set(new FileInputStream(file), MimeTypes.getContentType(file.getName()));
 
    doc.save();
    listUploads();
  }
 
  public static void listUploads()
  {
    List<Document> docs = Document.findAll();
    render(docs);
  }
 
  public static void downloadFile(long id)
  {
    final Document doc = Document.findById(id);
    notFoundIfNull(doc);
    response.setContentTypeIfNotSet(doc.file.type());
    renderBinary(doc.file.get(), doc.fileName);
  }
 
}

The uploadForm() method just causes the app/views/Files/uploadForm.html page to be displayed.

The doUpload() method handles the file upload and creates a new Document object that stores the file in S3 and the comment in a database. After storing the file and comment it runs the listUploads() method. Of-course a database must be configured in the conf/application.conf file. For running on Heroku the database is provided and just needs to be configured with the following values:

db=${DATABASE_URL}
jpa.dialect=org.hibernate.dialect.PostgreSQLDialect
jpa.ddl=update

The listUploads() method fetches all Document objects out of the database and then displays the apps/views/files/listUploads.html page.

If a user selects a file from the list then the downloadFile() method is called which finds the file in S3 and sends it back to the client as a binary stream. An alternative to this would be to get the file directly from Amazon using either the S3 generatePresignedUrl() method or via CloudFront.

Finally in the conf/routes file, requests to “/” have been mapped to the Files.uploadForm() method:

GET     /                                       Files.uploadForm

That’s it! Now we have an easy way to persist file uploads in an external system!

Running the Play! app on Heroku

If you’d like to run this example on Heroku, here is what you need to do:

Install the heroku command line client on Linux, Mac, or Windows.

Login to Heroku via the command line:

heroku auth:login

Clone the git repo:

git clone git@github.com:jamesward/plays3upload.git

Move to the project dir:

cd plays3upload

Create the app on Heroku:

heroku create -s cedar

Set the AWS environment vars on Heroku:

heroku config:add AWS_ACCESS_KEY="YOUR_AWS_ACCESS_KEY" AWS_SECRET_KEY="YOUR_AWS_SECRET_KEY" S3_BUCKET="AN_AWS_UNIQUE_BUCKET_ID"

Upload the app to Heroku:

git push heroku master

Open the app in the browser:

heroku open

Let me know if you have any questions or problems. And thanks to Peter, Nicolas, and Tim for helping with this!