GWT and JDO on the Java App Engine

I have been trying to build an application using GWT and JDO on the Java App Engine. This post captures some things I discovered along the way.

GWT-JDO compatibility
The JDO interface is provided by implementing extensions to DataNucleus. It provides a (not so seamless) wrapper around low level DataStore (BigTable) API. Before running the application, JDO annotated classes need to be bytecode-enhanced by the DataNucleus enhancer. (The appengine Eclipse plugin does this automatically). If you are writing a GWT app, then typically, these very JDO classes are also your GWT domain classes (myapp.client.DomainClass). So these classes need to be compatible with respect to gwt-compiler (e.g. Serializable) and data nucleus enhancer. An example of where this isn't obvious is in the choice of datatype for primary key. We can't simply use com.google.appengine.api.datastore.Key because the gwt-compiler cannot serialize Key (seems this is now possible) and we don't have the source for Key. Instead, we use an encoded String form of Key.

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class SomeDomainClass implements Serializable {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
String id;

User API integration with GWT
The appengine user API lets us authenticate Google users. However com.google.appengine.api.users.User cannot be used as a GWT domain object. gwt-compiler would need its source code. I ended up creating another class myapp.client.User for this purpose. This is probably good anyway because it lets me add app specific behavior to my User.

Development environment
GWT hosted mode gets going the quickest. Custom build file can be written to run local server mode. But neither of these are quite the same as deploying to the real appengine servers at appspot (or your domain). The local environment don't mimic all the characteristics of actual deployment. Post deployment testing is highly recommended for real apps. For purposes of unit testing, the datastore calls can be routed to an in-memory proxy. Also, Fred Sauer's gwt-log is quite handy.

JDO Quirks
Obviously there is no concept of joins. Mappings are also constrained. Pure one-to-one associations aren't supported. Instead, we have a form of composition that the appengine docs refer to as owned one-to-one relationship (e.g. Employee's ContactInfo). I am begining to wonder if the JDO interface is actually a hinderance to thinking non-relationally. It might be better to use the low level datastore API directly.

Overall, it has been fun. Java appengine seems quite promising. ThoughtWorks plug: Yes, we offer app dev and consulting services for all things cloudy.

Update: Coverage by fellow ThoughtWorkers:

Ola Bini
Paul Hammant
John Hume
Philip Calcado

3 comments:

Fred Sauer said...

I documented a workaround to the Key serialization or missing source code issue here: http://fredsa.allen-sauer.com/2009/04/1st-look-at-app-engine-using-jdo.html

Sudhir Jonathan said...

Dude, check out Objectify... I've been looking at at after struggling with JDO, and its like a breath of fresh air :D http://code.google.com/p/objectify-appengine/

José Francisco said...

Hi Sriram, I have the following source code:
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Customer implements IsSerializable {

@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private String name;
@Persistent
private Usuario admin;
.......
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Userimplements IsSerializable {

@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private String name;
.....

and I try to save as follows:

public boolean insertCustomer(Customer c, Usuer u) {
PersistenceManager pm = PMF.getPmfInstancia().getPersistenceManager();
Transaction tx = pm.currentTransaction();
try {
tx.begin();
c.setAdmin(u);
pm.makePersistent(c);
tx.commit();
....

but when I retrieve data, the customer is saved but the associated user not. Any idea?
Thanks.

Post a Comment