BestPractices - objectify/objectify GitHub Wiki
The first question you will have is "when and how should I register my entity classes?"
Entity POJO classes must be registered before Objectify is actually used. There are many ways to do this, but the most bulletproof is to use a ServletContextListener or an init servlet.
You might think that you could register an entity as a static initializer for the entity class itself:
public class ThingA
{
static { ObjectifyService.register(ThingA.class); }
// ... the rest of the entity definition
}
This is dangerous! Because Java loads (and initializes) classes on-demand, Objectify cannot guarantee that your class will be registered at the time that it is fetched from the database. For example, suppose you execute a query that might return several different kinds of entities:
Query<Object> things = ofy().load().ancestor(someParent); // could find both ThingA and ThingB entities
things.first().now(); // throws IllegalStateException!
When Objectify tries to reconstitute an object of type ThingA, it won't be able to because the ThingA class will not yet have been loaded and the static initializer will not have been called. If your application actually does use a ThingA before this query is executed, it will work - and in fact, it may work 99.99% of the time. But do you really want to hunt down mysterious IllegalStateExceptions 0.01% of the time?
Most J2EE-style frameworks, including Appengine's JDO/JPA system, do classpath scanning and can automatically register classes that have @Entity or other relevant annotations. This is convenient and could easily be added to Objectify without changing a single source file. There are, however, several reasons why this isn't part of the core:
- This feature requires either Scannotations or Reflections, bringing in 5-6 dependency jars.
- Developers would need to add a startup hook to your web.xml (a ServletContextListener) in order to trigger this scanning.
- Classpath scanning is slow because it opens each .class and .jar file in your project and processes every single class file with a bytecode manipulator. For a moderately sized project this easily adds 3-5 seconds to your application initialization time. That's 3-5 additional seconds that real-world users must sit waiting while your application cold-starts.
Of these issues, the last is the most fatal. If you think "My application gets a lot of traffic! I don't need to worry about cold starts!", you are overlooking the fact that App Engine starts and stops instances to meet demand all the time - at least one user somewhere is going to be affected on every spinup. Plus this happens every time you redeploy your application! There is no escaping cold-start time.
Furthermore, classpath scanning costs accumulate. If you use other tools that perform classpath scanning (Weld, Spring, JAX-RS, etc), they each will also spend 3-5s scanning your jars. It isn't hard to push your cold-start time into the tens of seconds.
That said, 3-5s might be reasonable for your specific project. It should be very easy to add as your own ServletContextListener that calls Reflections and registers the @Entity classes. Spring and other framework users should examine the Extensions.
In SQL, all data lives in tables and is accessed through queries. It is best not to imagine the Appengine datastore this way - conceptually shift to thinking of the datastore as a key-value store that happens to also let you index and query some values.
The reason this shift is important is because your most effective tool when working with chunks of data is the batch load and save. A batch get-by-key will fetch many entities in parallel; running individual queries for each would take a relative eternity. Asynchronous queries can only provide limited help because GAE limits you to 10 concurrent requests.
Furthermore, a batch get-by-key can be efficiently cached. Use Objectify's @Cache
annotation and your load() may never need a trip to the datastore.
Of course, batch gets and queries are not necessarily fungible operations - but when they are, use a batch get.
Each index adds cost. There is an up-front cost (two writes per newly-written single-property index; four if you change an index value) plus ongoing storage fees. For most GAE apps, most of your storage costs will be index data - it grows quickly.
Objectify does not index anything by default; you must explicitly request it with the @Index
annotation. Use it wisely.
Note: These are hopelessly out of date.
-
IBM developerWorks' Twitter Mining with Objectify-Appengine, part 1 and part 2
-
Google I/O 2008 - Under the Covers of the Google App Engine Datastore (required watching for anyone that uses App Engine!)
-
Differences between Twig and Objectify plus example of million user fanout
-
David M. Chandler's blog posting about Objectify and Objectify 2
-
How to emulate @Unique and a longer discussion of enforcing uniqueness.
-
Simple tutorial for 'putting' data in the datastore using Objectify