http://code.google.com/p/objectify-appengine/

I probably should have called this project "Goldilocks", because it's
a little bit how I feel.  Despite being a longtime Hibernate user
(since the 1.0 days), the JDO/JPA abstraction just doesn't make me
happy on appengine - it's too big, too complicated, and too far
removed from the nature of the beast.  The low-level API has an
elegant simplicity, but it lacks type safety.  I tried the
alternatives - Siena, Twig, SimpleDS, and Slim3.  There are good
things I can say about these projects but none were "just right" for
me.

So, in time-honored tradition, I wrote my own and now I'm offering it
to the world.  Here is what "just right" means to me.  Maybe it's just
right for you too:

 * An interface that reflects the four fundamental datastore
operations - get, put, delete, query - including their batch variants.

 * Persisting real typed POJO classes - no detaching, no lifecycle,
serialize at will.

 * Keys in Objectify are generified and typed.  Instead of Key you use
OKey<MyEntity>.  This generic typing extends to OQuery<MyEntity> and
OPreparedQuery<MyEntity>.

 * Because Kind ~= POJO Class, Key should not be used for an object's
id.  The object's id and its (optional) parent plus the class is
complete; an entity that has a Key identifier contains a redundant
(and potentially inaccurate) kind.  Nevertheless, a Key is necessary
for loading entities or referencing entities, and forms a fundamental
part of the API.  This dichotomy is something I don't feel had been
done right yet.

 * Queries are modeled after the human-friendly GAE/Python Query
class:  query.filter("field >", 123).sort("-field").  You can filter
and sort on id fields almost as if they are normal properties.

 * Transactional behavior is contained within the Objectify interface
(analogous to DatastoreService) instance rather than a thread local.
You can easily have several transactions (or nontransactional
sessions) running concurrently.

 * You can use your entities in GWT-RPC without modification - even
with OKey fields.  They're just POJOs and they serialize fine.

 * Builtin facilities to help with renaming fields and transforming
data during schema migration.

 * Configurable automatic retries for DatastoreTimeoutExceptions
(finally get rid of the 0.1% trickle of failures!).

 * Zero external dependencies - no Spring, no Guice, not even a logger
(it just wasn't necessary). Just one lonely 36K jar.

 * Objectify will work nicely with your DI framework.  Static
singletons are not required.

 * Negligible impact on cold start time.

 * Thorough unit test suite.

 * Simple, easy-to-read, well-documented code.  Not counting the
tests, there's actually only ten source files plus three annotations.
About 2,000 lines including whitespace and javadocs.

There is ample documentation at
http://code.google.com/p/objectify-appengine/, but some code examples
should make this clear:

Basic operations:

@Entity
class Car {
  �...@id String vin; // Can be Long, long, or String
   String color;
   Date registered;
}

Objectify ofy = ObjectifyService.begin();
ofy.put(new Car("123123", "red"));
Car c = ofy.get(Car.class, "123123");
ofy.delete(c);

OQuery<Car> query = ObjectifyService.newQuery(Car.class);
query.filter("registered <", lastYear).sort("color");
List<Car> cars = ofy.prepare(query).asList();

Some more sophsticated examples:

@Entity
class Employee {
  �...@id long id; // primitive long is never autogenerated
  �...@parent OKey<Company> employer;

   // field getting renamed
  �...@oldname("boss") OKey<Employee> manager;

   String firstName;
   String lastName;

   // we used to store fullName, now we store first and last separately
  �...@oldname("fullName") public void oldWay(String fullName)
   {
       String[] split = fullName.split(" ");
       firstName = split[0];
       lastName = split[1];
   }
}

Here's an example of interleaving a transactional session with a
nontransactional session:

Objectify ofyNoTxn = ObjectifyService.begin();
Objectify ofyTxn = ObjectifyService.beginTransaction();
try
{
       Foo f = ofyTxn.get(Foo.class, "k123");
       Bar b = ofyNoTxn.get(f.barKey);

       if (b.wantsUp())
               f.increment();
       else
               f.decrement();

       ofyTxn.put(f);

       ofyTxn.getTxn().commit();
}
finally
{
       if (ofy.getTxn().isActive())
               ofy.getTxn().rollback();
}

The full docs are here:

http://code.google.com/p/objectify-appengine/

What doesn't it do?

 * It doesn't work with any datastore other than GAE.

 * It doesn't manage relationships for you - if you want cascading
deletes, collection proxies, and all the advantages and disadvantages
of that - you want JDO/JPA.  I find managing this myself pretty easy,
and well worth the transparency.

 * No support yet for polymorphism.  It won't require major structural
changes to add though.

 * It doesn't suck.  Or so I am told.

Jeff
-- 
You received this message because you are subscribed to the Google Groups 
"Google App Engine for Java" group.
To post to this group, send email to google-appengine-j...@googlegroups.com.
To unsubscribe from this group, send email to 
google-appengine-java+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/google-appengine-java?hl=en.


Reply via email to