BestPractices - objectify/objectify GitHub Wiki

Best Practices

Registering Your Entities

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.

How NOT To Register Entities

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?

Automatic Scanning

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:

  1. This feature requires either Scannotations or Reflections, bringing in 5-6 dependency jars.
  2. Developers would need to add a startup hook to your web.xml (a ServletContextListener) in order to trigger this scanning.
  3. 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.

Use Batch Gets Instead of Queries

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.

Use Indexes Sparingly

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.

Interesting discussions related to Objectify

Note: These are hopelessly out of date.

⚠️ **GitHub.com Fallback** ⚠️