Sunday, November 08, 2009

Gachi - Query interface added

GAE/j has a built-in Query concept which I have extended into a Gachi Query interface through which all queries must be executed.

At my very first test on the newly implemented interface I ran into a well known "feature" of GAE/j, the "can't operate on multiple entity groups in a single transaction" problem. This exception is thrown when you try to operate in more than one entity-group within a single transaction (as the text describes pretty well :-) ), but I simply just queried for all Wishlists (my example), which are all entity-roots, and then I did a DatastoreService.get(key) using the key returned from the query. "WTF!" I thought - "Can't I query multiple entity-roots? Then how will I list all Wishlists?".. This is infact quite tricky, but I've added a "keysOnly" switch to my Query interface, which will, if set, only work within a single entity-root, but if unset, you may Query mutiple entity-roots, but only see properties (as of now)..

Next up - lazy load of ManyAssociation items (currently they are all instanciated when first accessed)

Friday, November 06, 2009

Gachi - cascade deletes implemented

I've written a roadmap for Gachi and one of the biggest steps was to implement cascade deletes and it's DONE! (as in working :-) )

Since Google App Engine's datastore is kinda hierarchical but then not really, you have to delete all child entities yourself, when you delete the "parent" entity. I'm really pleased with my implementation where I've introduced a meta-info concept where I, when deleting an entity, knows which child entities I must also delete.

Next up - Query interface

Sunday, November 01, 2009

Gachi - first look

Well - as mentioned in previous blog entry I've written a tiny framework to make it easier to store (kinda) POJO's in Google App Engine for Java. This will try to explain what I've got running so far:


@Test
public void testCreateWishlistAndAddRecipient() {
UnitOfWork uow = new GAEUnitOfWork();

All work with entities are done in the scope of a unit of work (UnitOfWork) which is transactional and only for a single entity-group at a time, since GAE/J can only handle transactions within a single entity-group, its very important not to hide this fact from users.

Date created = new Date();

Wishlist wishlist = uow.newEntity(Wishlist.class);
wishlist.title().set("Wishlist for christmas 2009");
wishlist.comment().set("No soft presents");
wishlist.created().set(created);

To create a new entity (in this case entity-group as there are no parents) you use "newEntity" which will return an instance of the class given as second argument.

uow.complete();

"uow.complete" will make all changes persistent using GAE low-level API.

uow = new GAEUnitOfWork();
wishlist = uow.get(wishlist.getEntityReference());
assertEquals("Wishlist for christmas 2009", wishlist.title().get());
assertEquals("No soft presents", wishlist.comment().get());
assertEquals(created, wishlist.created().get());

Well - check to make sure all is persistent as expected...

Recipient recipient1 = uow.newEntity(wishlist, Recipient.class);
recipient1.name().set("Test Person #1");
recipient1.email().set(new Email("testperson1@email.com"));

In this example a Wishlist holds a ManyAssociation which is a collection of Recipient objects. The above creates a new Recipient, which will be stored as a child GAE Entity of the Wishlist Entity (not a Key reference) and therefore it's parent (wishlist) if given as argument in "newEntity(wishlist, Recipient.class)

Recipient recipient2 = uow.newEntity(wishlist, Recipient.class);
recipient2.name().set("Test Person #2");
recipient2.email().set(new Email("testperson2@email.com"));

wishlist.recipients().add(recipient1);
wishlist.recipients().add(recipient2);

uow.complete();

Finally the newly created 2 Recipient objects are added to the "wishlist.recipients()" ManyAssociation (collection) and UnitOfWork is complete (all objects persistet)

uow = new GAEUnitOfWork();
wishlist = uow.get(wishlist.getEntityReference());
ManyAssociation recipients = wishlist.recipients();
assertEquals(2, recipients.getCount());
assertEquals("Test Person #1", recipients.get(0).name().get());
assertEquals("testperson1@email.com", recipients.get(0).email().get().getEmail());
assertEquals("Test Person #2", recipients.get(1).name().get());
assertEquals("testperson2@email.com", recipients.get(1).email().get().getEmail());
}

... and again tests to check all were persistet as expected.

Next up - a look under the hood where child objects are all lazy loaded.