Install SBT
Simple Build Tool (SBT) is a build tool for Scala, and it's just super. To install it, download the jar from here: http://code.google.com/p/simple-build-tool/downloads/list
Put that jar someplace and the make a little script along the lines of this:
java -Xmx1512M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=512m -jar `dirname $0`/sbt-launch.jar "$@"Save that alongside the jar you downloaded. Make it executable and add it to your PATH.
MongoDB
MongoDB ( http://www.mongodb.org ) is a document database, meaning it persists JSON-like documents of arbitrary structure. It's very fast and offers very flexible ad hoc querying. A MongoDB document looks like this:
{You don't create joins in the database - each document is schema-less and unrelated to other documents, even those in its collection (sort of like a SQL table in MongoDB parlance), But you can do cool stuff like index on those nested comments. Here's how to install it and get started:
_id : ObjectId("4d2167a24d2fa1a764a6f0fa"),
date : "Sun Jan 02 2011 22:07:30 GMT-0800 (PST)",
body : "My blog post!",
upvotes : 3,
downvotes : 0,
comments : [
{
author : "The Dude",
body : "The Dude abides"
},
{
author : "Walter",
body : "Over the line!"
}
]
}
Download the tarball for your architecture: http://www.mongodb.org/downloads
Expand it somewhere and add the bin directory to your PATH if you want to.
Run something like this to start the MongoDB server:
$MONGO_HOME/bin/mongod --dbpath=/path/to/place/to/save/mongodb/dataConnect to it and try it out like so:
$MONGO_HOME/bin/mongo'mongo' is the MongoDB shell, which is a complete Javascript environment and connects to the local server on the default port. Create the example document above like this:
> use tempIt's an excellent tool and a lot of fun. There are great docs at http://mongodb.org
switched to db temp
> db.demo.insert({date:new Date(), body:'My blog post!', upvotes: 3, dowvotes: 0, comments: [{author: 'The Dude', body: 'The Dude abides'},{author: 'Walter', body: 'Over the line!'}]})
> db.demo.find()
Creating the SBT Project
Let's create an application. Make yourself a directory, navigate to it and run sbt. It will guide you through a wizard experience thusly:
$ sbtLeave that shell open to the sbt prompt. In your editor of choice, create a file in your project directory named
Project does not exist, create new project? (y/N/s) y
Name: Demo
Organization: com.demo
Version [1.0]:
Scala version [2.7.7]: 2.8.1
sbt version [0.7.4]:
Getting Scala 2.7.7 ...
...more stuff...
[success] Successfully initialized directory structure.
...more stuff...
[info] Building project Demo 1.0 against Scala 2.8.1
[info] using sbt.DefaultProject with sbt 0.7.4 and Scala 2.7.7
>
project/build/Project.scalaAdd the following to it:
import sbt._In your sbt shell, run
class build(info: ProjectInfo) extends DefaultWebProject(info) {
// scalatra
val sonatypeNexusSnapshots = "Sonatype Nexus Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
val sonatypeNexusReleases = "Sonatype Nexus Releases" at "https://oss.sonatype.org/content/repositories/releases"
val scalatra = "org.scalatra" %% "scalatra" % "2.0.0.M2"
// jetty
val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.22" % "test"
val servletApi = "org.mortbay.jetty" % "servlet-api" % "2.5-20081211" % "provided"
//casbah
val casbah = "com.mongodb.casbah" %% "casbah" % "2.0.1"
}
reloadand then
updateSBT will download dependencies ending with a message of success.
Add a Scalatra GET handler
Scalatra is refreshingly simple. To create a Scalatra servlet with a simple GET handler, using Scala's support for XML literals, create a file like src/main/scala/WebApp.scala with the following content:
import org.scalatra._web.xml
class WebApp extends ScalatraServlet {
get("/hello") {
<html><head><title>Hello!</title></head><body><h1>Hello</h1></body></html>
}
}
One last bit of setup is to add the Scaltra servlet to the web.xml. Create a file named /src/main/webapp/WEB-INF/web.xml with the following content:
<?xml version="1.0" encoding="UTF-8"?>Back at the SBT prompt you just need to
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<servlet>
<servlet-name>WebApp</servlet-name>
<servlet-class>WebApp</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WebApp</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
reloadand then do the following to start jetty and listen for source file changes:
jetty-runOpen a browser to http://localhost:8080/hello and you should get a working Scalatra app. Change the message to something more interesting, like "Hello World" or "Goodnight Moon", save the source file, and SBT will recompile and redeploy. Just reload the web browser to see your changes.
~prepare-webapp
Parameters
Add a path parameter to your GET call thusly:
Save the file, browse to http://localhost:8080/hello/handsome
get("/hello/:name") {
<html><head><title>Hello!</title></head><body><h1>Hello {params("name")}!</h1></body></html>
}
Oh yeah!
A Form
Add a form like this:
get("/msgs") {You can see the fruits of your labor at http://localhost:8080/msgs
<body>
<form method="POST" action="/msgs">
Author: <input type="text" name="author"/><br/>
Message: <input type="text" name="msg"/><br/>
<input type="submit"/>
</form>
</body>
}
Casbah
Casbah is the officially supported Scala driver for Mongo. It wraps the Java driver and makes accessing MongoDB straightforward using idiomatic Scala. http://api.mongodb.org/scala/casbah/2.0.2/index.html
You already have the driver dependency in your project, but you'll need a few new imports:
import com.mongodb._And a MongoDB connection that can be shared across the application:
import com.mongodb.casbah.Imports._
import scala.xml._
val mongo = MongoConnection()Add a method to handle the form POST:
val coll = mongo("blog")("msgs")
Slick, right? That's all it takes to add a new document to the "msgs" collection in the "blog" db. You don't even need to create the db and collection first - just make sure MongoDB is running on the local host at the default port 27017.
post("/msgs") {
val builder = MongoDBObject.newBuilder
builder += "author" -> params("author")
builder += "msg" -> params("msg")
coll += builder.result.asDBObject
redirect("/msgs")
}
Now you should adjust the form page to display the existing messages. This code treats iterates over the whole collection - in a real-world application you'd want paging, querying, filtering etc.
Add this code to your get() method just after the body tag:
<ul>
{for (l <- coll) yield <li>
From: {l.getOrElse("author", "???")} -
{l.getOrElse("msg", "???")}</li>}
</ul>
Once your app builds and you reload the page, your form should work to add new messages with authors and display them.
In Conclusion
I've found this stack a great way to whip up a quick example, prototype or test. You can use SBT to build a war (the 'package' command) and deploy it to any container you want.
I've barely scratched the surface of any of these bits, but hopefully piqued some interest in some cool tools. There are great docs and examples for all of these projects, so dig in and have fun!
Source code:
project/build/Project.scala:
import sbt._
class build(info: ProjectInfo) extends DefaultWebProject(info) {
// scalatra
val sonatypeNexusSnapshots = "Sonatype Nexus Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
val sonatypeNexusReleases = "Sonatype Nexus Releases" at "https://oss.sonatype.org/content/repositories/releases"
val scalatra = "org.scalatra" %% "scalatra" % "2.0.0.M2"
// jetty
val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.22" % "test" servletApi = "org.mortbay.jetty" % "servlet-api" % "2.5-20081211" % "provided"
//casbah
val casbah = "com.mongodb.casbah" %% "casbah" % "2.0.1"
}
src/main/scala/WebApp.scala:
import org.scalatra._
import com.mongodb.casbah.Imports._
import scala.xml._
import com.mongodb._
class WebApp extends ScalatraServlet {
val mongo = MongoConnection()
val coll = mongo("blog")("msgs")
get("/hello/:name") {
<html><head><title>Hello!</title></head><body><h1>Hello {params("name")}!</h1></body></html>
}
get("/msgs") {
<body>
<ul>
{for (l <- coll) yield <li>
From: {l.getOrElse("author", "???")} -
{l.getOrElse("msg", "???")}</li>}
</ul>
<form method="POST" action="/msgs">
Author: <input type="text" name="author"/><br/>
Message: <input type="text" name="msg"/><br/>
<input type="submit"/>
</form>
</body>
}
post("/msgs") {
val builder = MongoDBObject.newBuilder
builder += "author" -> params("author")
builder += "msg" -> params("msg")
coll += builder.result.asDBObject
redirect("/msgs")
}
}
Very nice intro; well organized and easy to follow. Scalatra looks pretty neat for simple Web applications.
ReplyDeleteVery good article. Thanks.
ReplyDeleteError running Jetty: java.net.BindException: Address already in use
ReplyDelete^ if you get that (referring to port 8080), add this line to the class in Project.scala:
override val jettyPort = 9999
See http://code.google.com/p/simple-build-tool/wiki/WebApplications for more about jetty configuration.
Great article, i'd always tip toed around trying a light weight web framework like sinatra/scalatra but i'll definitely be giving it a whirl now. VERY simple.
ReplyDelete