Play with MongoDB

For the normal data storage in most of todays applications a relational DB is not the optimal solution for several reasons. There are many great alternatives now on the market. I like MongoDb because it is not that far away from the concepts of a relational DB, it is easy to set up and has proven to be stable and reliable.

Thus I clearly want a MongoDb as persistence in my Play 2! project. Goal is

  • to have a good abstraction. Not too much magic but not too much boilerplate either
  • to have it running locally and in the cloud (I’ll go for Heroku here)

I found Salat a pretty good abstraction. It got it running quickly locally. But when I tried to get it running on Heroku it became a little awkward. At Heroku – as on every other PaaS I had a look at – you get a URL for the MongoDb you added to your app, normally provided as system property. Same holds if rent a DB in the cloud at a “pure” cloud DB provider. But there are no means (that I know of course) to easily let Salat read and use the URL.

So I wrote a MongoDao that does the following things:

  • it reads a MongoURL defined in the application.conf via the mongo.uri property.

In my template application I provided an example that reads the environment variable MONGOLAB_URI which is the system variable on a Heroku app with an installed MongoLab instance. If no such variable is set, the property is left empty. This lets the MongoDao take the standard parameters for a local MongoDb installation.

  • a collection method that returns a collection as required by the SalatDao, so you can easily define the SalatDao in your implementation
  • if you want to differentiate between different local DBs you can overwrite the defaultDbName val in you class.

This is pretty much it. If any one of you has good ideas to make the abstraction more convenient, clean, elegant I’d be happy to read your comments.

Getting Play 2 with Scalate to run on heroku

After having everything in place I thought it’d be a great idea to deploy the small app in the cloud. After some research I was really excited about Heroku. In my eyes a brilliant idea and realization of PaaS.
After the decision for Heroku it took me only a couple of minutes, till I was ready to go,  with the quite  good tutorial of the heroku guys. The push worked flawlessly. But when I wanted to admire my first app in the cloud, I got an error message on the page. Shouldn’t be too easy anyway, right?
The logs told me the following:

2012-04-18T06:00:44+00:00 app[web.1]: ! @6a4hnb7bj - Internal server error, for request [GET /] ->
2012-04-18T06:00:44+00:00 app[web.1]: 	at org.fusesource.scalate.TemplateEngine.compileAndLoad(TemplateEngine.scala:834) ~[scalate-core-1.5.3.jar:1.5.3]
2012-04-18T06:00:44+00:00 app[web.1]:
2012-04-18T06:00:44+00:00 app[web.1]: 	at org.fusesource.scalate.TemplateEngine.compileAndLoadEntry(TemplateEngine.scala:691) ~[scalate-core-1.5.3.jar:1.5.3]
2012-04-18T06:00:44+00:00 app[web.1]: 	at org.fusesource.scalate.util.IOUtil$.writeBinaryFile(IOUtil.scala:111) ~[scalate-util-1.5.3.jar:1.5.3]
2012-04-18T06:00:44+00:00 app[web.1]: 	at akka.actor.Actor$class.apply(Actor.scala:290) [akka-actor-2.0.jar:2.0]
2012-04-18T06:00:44+00:00 app[web.1]: 	at org.fusesource.scalate.layout.DefaultLayoutStrategy.org$fusesource$scalate$layout$DefaultLayoutStrategy$$tryLayout(DefaultLayoutStrategy.scala:77) ~[scalate-core-1.5.3.jar:1.5.3]
2012-04-18T06:00:44+00:00 app[web.1]: [error] o.f.s.l.DefaultLayoutStrategy - Unhandled: org.fusesource.scalate.TemplateException: target/../tmp/src/app/target/../app/views/layouts/default.scaml.scala (No such file or directory)
2012-04-18T06:00:44+00:00 app[web.1]: play.core.ActionInvoker$$anonfun$receive$1$$anon$1: Execution exception [[TemplateException: target/../tmp/src/app/target/../app/views/layouts/default.scaml.scala (No such file or directory)]]
2012-04-18T06:00:44+00:00 app[web.1]: 	at org.fusesource.scalate.TemplateEngine.liftedTree1$1(TemplateEngine.scala:411) ~[scalate-core-1.5.3.jar:1.5.3]
2012-04-18T06:00:44+00:00 app[web.1]: 	at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:63) [play_2.9.1-2.0.jar:2.0]
2012-04-18T06:00:44+00:00 app[web.1]: 	at org.fusesource.scalate.TemplateEngine.load(TemplateEngine.scala:475) ~[scalate-core-1.5.3.jar:1.5.3]
2012-04-18T06:00:44+00:00 app[web.1]: 	at play.core.ActionInvoker.apply(Invoker.scala:61) [play_2.9.1-2.0.jar:2.0]
2012-04-18T06:00:44+00:00 app[web.1]: Caused by: java.io.FileNotFoundException: target/../tmp/src/app/target/../app/views/layouts/default.scaml.scala (No such file or directory)
2012-04-18T06:00:44+00:00 app[web.1]:
2012-04-18T06:00:44+00:00 app[web.1]: Caused by: org.fusesource.scalate.TemplateException: target/../tmp/src/app/target/../app/views/layouts/default.scaml.scala (No such file or directory)
2012-04-18T06:00:44+00:00 app[web.1]: 	at akka.actor.ActorCell.invoke(ActorCell.scala:617) [akka-actor-2.0.jar:2.0]
2012-04-18T06:00:44+00:00 app[web.1]: 	at java.io.FileOutputStream.(FileOutputStream.java:160) ~[na:1.6.0_20]
2012-04-18T06:00:44+00:00 app[web.1]: 	at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:82) [play_2.9.1-2.0.jar:2.0]
2012-04-18T06:00:44+00:00 app[web.1]: 	at org.fusesource.scalate.TemplateEngine.load(TemplateEngine.scala:405) ~[scalate-core-1.5.3.jar:1.5.3]
2012-04-18T06:00:44+00:00 app[web.1]: [error] application -
2012-04-18T06:00:44+00:00 app[web.1]: 	at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:179) [akka-actor-2.0.jar:2.0]
2012-04-18T06:00:44+00:00 app[web.1]: 	at java.io.FileOutputStream.open(Native Method) ~[na:1.6.0_20]
2012-04-18T06:00:44+00:00 app[web.1]: 	at org.fusesource.scalate.TemplateEngine.compileAndLoad(TemplateEngine.scala:747) ~[scalate-core-1.5.3.jar:1.5.3]
2012-04-18T06:00:44+00:00 app[web.1]: 	at java.io.FileOutputStream.(FileOutputStream.java:209) ~[na:1.6.0_20]
2012-04-18T06:00:44+00:00 app[web.1]: 	at org.fusesource.scalate.TemplateEngine.compileAndLoadEntry(TemplateEngine.scala:691) ~[scalate-core-1.5.3.jar:1.5.3]

It took me a while to find out what the problem was. The scalate integrating code contained paths like “/app/views” which worked perfectly on my machine but wasn’t on heroku. Deleting the first slash solved the problem.

While investigating the problem I thought that the problem might come from the not yet precompiled Scamls. So I enabled precompilation for Scalate. This can be done as follows:

Add the xsbt scalate generator to the plugins.sbt of your project:

libraryDependencies  "com.mojolly.scalate" %% "xsbt-scalate-generator" % (v + "-0.1.6"))

Additionally a build.sbt must be added to the home directory of the project with the following content:

import com.mojolly.scalate.ScalatePlugin._

seq(scalateSettings:_*)

scalateTemplateDirectory in Compile <<= (baseDirectory) { _ / "app/views" }

//scalateImports ++= Seq(
//  "import OAuth2Imports._",
//  "import model._"
//)
//
//scalateBindings ++= Seq(
//  Binding("flash", "scala.collection.Map[String, Any]", defaultValue = "Map.empty"),
//  Binding("session", "org.scalatra.Session"),
//  Binding("sessionOption", "Option[org.scalatra.Session]"))

The commented parts are bindings and “auto-imports” that might become handy if you need em. If you don’t use the e.g. oauth libraries, you’ll of course get compilation errors as the imports are added to the templates. I’ve left some examples in the file as a reminder that and how it is possible to use these features.

As precompilation is something you should have enabled in production anyway, I left it enabled after finding out why the app on heroku didn’t run.

Coming up

So far all entered data is “persisted” to memory. To change this I’ll use MongoDB, as this is an excellent alternative for a classic SQL.