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.