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 [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-appengine-java?hl=en.