Wednesday, 13 May 2009

Data Persistence in GAE with Clojure

If you want to persist stuff in Java, you've got a bewildering amount of choice

There's even an entire book about making the right decision! (Persistence in the Enterprise)

Google App Engine has gone with JDO using the Data Nucleus platform. In GAE this is split again into two APIs, the high-level one for persisting objects, and a lower-level one which allows you to persist raw data.

When using Clojure it makes more sense to go with the lower-level api. The higher-level one would require using annotations on objects which isn't supported at the moment in Clojure (as far as I know!).

So how do we store data in GAE? The example below saves a story to disk (as a learning exercise, I'm writing a quick and dirty reddit clone).

(ns news.savestory
(:use (news appengine))
(:gen-class :extends javax.servlet.http.HttpServlet)
(:import ( DatastoreServiceFactory Entity Key Query)))

(defn store
[data type]
(let [entity (Entity. (.toString type))]
(doseq [[k v] data]
(.setProperty entity (.toString k) v))
(.put (DatastoreServiceFactory/getDatastoreService) entity)
(.getKey entity)))

(defn -doGet
[_ request response]
(let [body (.getParameter request "storyLink")
title (.getParameter request "storyTitle")]
(let [w (.getWriter response)]
(.println w (store {:body body :title title} :story)))))

store takes a map and a type and persists it in the database and returns the key associated with this entity. Visiting the URL persists the data in the URL and returns the key.

Retrieving the data is much the same.

(ns news.viewstory
(:use (news appengine))
(:gen-class :extends javax.servlet.http.HttpServlet)
(:import ( DatastoreServiceFactory Entity Key Query KeyFactory)))

(defn entity-to-map
(into (hash-map) (.getProperties entity)))

(defn getEntity
[id type]
(let [k (KeyFactory/createKey (.toString type) (Long/valueOf id))]
(.get (DatastoreServiceFactory/getDatastoreService) k))))

(defn -doGet
[_ request response]
(let [id (.getParameter request "storyId")
story (getEntity id :story)
w (.getWriter response)]
(doseq [[k v] story]
(.println w (str k "=" v)))))

entity-to-map just converts the properties of the entity into a friendly Clojure type.

So now I know how to authenticate users, next step is to get some basic UI together. There's a number of choices here (JSP, server side HTML generation in Clojure or just go with Ajax). I'm leaning towards the Ajax!