Here's the Objectify version of what's described in the video, capable
of a photo-equivalent of "million user fanout":

class Album {
   @Id Long id;
   String name;
}
class PhotoIndex {
   @Id Long id;
   @Parent OKey<Album> album;
   Set<OKey<Photo>> photos;
}
class Photo {
   @Id Long id;
   String caption;
   String blobStoreKey;
}

If you want to ask "what albums are this photo in?" (equivalent to
"what messages are waiting for me" in the video), you query like this:

OQuery<PhotoIndex> query =
createQuery(PhotoIndex.class).filter("photos", photoKey);
List<OKey<Album>> keys = ofy.prepareKeysOnly(query).asList();
List<Album> albums = ofy.get(keys);

Jeff

On Tue, Jan 26, 2010 at 4:08 PM, Duong BaTien <[email protected]> wrote:
> Hi Jeff:
>
> I am here again and have put sometime in Objectify. Thanks for taking
> pain at different design patterns. Please let the list known your effort
> in the good idea #2, especially in the social graph set intersections
> and union.
>
> Duong BaTien
> DBGROUPS and BudhNet
>
>
> On Tue, 2010-01-26 at 14:19 -0800, Jeff Schnitzer wrote:
>> On Mon, Jan 25, 2010 at 11:52 PM, John Patterson <[email protected]> 
>> wrote:
>> >
>> > This is why you configure what type of relationship is used using: @Embed,
>> > @Entity(PARENT), @Entity(CHILD) or @Entity(INDEPENDENT)
>> > So you have the flexibility to choose configuration _without_ rewriting 
>> > your
>> > code.  Very important difference.
>>
>> The problem is that it *isn't* as simple as just changing an
>> annotation.  Not in appengine, at any rate.  It works in the simple
>> case (good for demos and sample apps), but you start to notice edge
>> cases:
>>
>>  * In some representations you can add a Photo to an Album in a
>> transaction, in some representations you can't.
>>  * In some representations, multiple queries are required to fetch a
>> fetch an Album containing a Photo.
>>  * Each representation has a completely different query syntax.
>>
>> Twig exposes the datastore Query object, which means the developer
>> gets the full brunt of this exposure.
>>
>> I don't want to say that it isn't possible to build a system that
>> abstracts entities into a sophisticated object graph - clearly, we
>> have JDO & JPA.  What I'm saying is that the people who created JDO &
>> JPA are not idiots (although I do think the creators of JDO's
>> annotations are aesthetically challenged).  The reason JDO has all
>> that complexity and endless configuration and query languages and
>> fetch groups and proxies and detaching and whatnot is because that's
>> what you need to abstract an arbitrary entity graph.
>>
>> > Currently the first type of representation is not an option.  I do want to
>> > add this as it makes very large collections that change often much more
>> > efficient.  When it is added you could reconfigure your data schema by
>> > changing a single annotation.  Such a change in Objectify would require the
>> > developer to rewrite their entire data layer
>>
>> You can never just reconfigure your data schema with a single
>> annotation, both for the reasons above and because you probably have
>> real-world data to migrate.  And real-world constraints demand a
>> particular schema!
>>
>> Let's actually answer the original poster's question - how do you
>> model a photo album?  I hope he's still listening :-)
>>
>> The first question is how you should model it in the datastore?  I'll
>> use Objectify's syntax  here because it corresponds directly to the
>> datastore representation.
>>
>> Actually, let's start by describing how you SHOULDN'T model a photo album.
>>
>> -----
>>
>> BAD IDEA #1:
>>
>> class Album {
>>     @Id Long id;
>>     String name;
>>     List<OKey<Photo>> photos;
>> }
>> class Photo {
>>     @Id Long id;
>>     String caption;
>>     String blobStoreKey;    // key to GAE's blobstore
>> }
>>
>> Fetching photos in an album (again, Objectify syntax but equivalent to
>> the datastore operation) is:
>>
>> List<Photo> fetched = ofy.get(album.photos);
>>
>> There are two reasons why this is a bad idea:
>>
>>  1) You now have a hard limit of 5,000 photos per album, established by GAE.
>>
>>  2) Every time you load an Album, you must load the entire set of
>> Photo keys.  Want to generate a list of Album names?  You have to load
>> all that key data, orders of mangitude more data than what you want.
>>
>> -----
>>
>> BAD IDEA #2:
>>
>> class Album {
>>     @Id Long id;
>>     String name;
>> }
>> class Photo {
>>     @Id Long id;
>>     @Parent OKey<Album> album;
>>     String caption;
>>     String blobStoreKey;
>> }
>>
>> This stores the Photo with the Album embedded in the Photo's Key as an
>> ancestor, making Photo part of the Album's entity group.  At first
>> glance, this seems kinda cool and you can now do transactions across
>> Albums and Photos.
>>
>> Fetching photos in an album:
>>
>> OQuery<Photo> query = createQuery(Photo.class).ancestor(albumKey);
>> List<Photo> fetched = ofy.prepare(query).asList();
>>
>> The problem is what happens when you want to move a Photo from one
>> Album to another.  You can't just change the parent.  You must delete
>> the Photo entity and create a whole new Photo entity with the new
>> parent Album.  And if the Photo has Comments or other referring
>> entities?  All those comments need to be repointed at the new Photo.
>> A mess.
>>
>> -----
>>
>> GOOD IDEA:
>>
>> class Album {
>>     @Id Long id;
>>     String name;
>> }
>> class Photo {
>>     @Id Long id;
>>     OKey<Album> album;
>>     String caption;
>>     String blobStoreKey;
>> }
>>
>> Fetching photos in an album:
>>
>> OQuery<Photo> query = createQuery(Photo.class).filter("album", albumKey);
>> List<Photo> fetched = ofy.prepare(query).asList();
>>
>> You can now move Photos between Albums easily and you can load/query
>> Albums efficiently.
>>
>> -----
>>
>> GOOD IDEA #2:
>>
>> I considered writing something about index entities as described here:
>> http://code.google.com/events/io/2009/sessions/BuildingScalableComplexApps.html
>>
>> This is what you would probably want to use if a Photo can live in
>> more than one Album.  But this message is long enough already.
>>
>> -----
>>
>> So now you're thinking, that's just the representation in the
>> datastore, wouldn't you rather have a entities that hide all that and
>> provide an interface like this:
>>
>> class Album {
>>     @Id Long id;
>>     String name;
>>     List<Photo> photos;
>> }
>> class Photo {
>>     @Id Long id;
>>     String caption;
>>     String blobStoreKey;
>> }
>>
>> This is one of those things that looks good in a simple demo but in
>> the real world breaks down.  Twig doesn't currently support mapping
>> this to GOOD IDEA.  If Twig did support this, it would also need to
>> support lazy loading of the collection with a proxy - otherwise you
>> have a worse problem than BAD IDEA #1!  And now, if this list is a
>> proxy, serialization becomes an issue - you need some sort of
>> detaching mechanism.
>>
>> ...and then you have JDO.
>>
>> I hope this message doesn't sound like "Twig is bad" - it's not, and
>> it does (and probably always will) do things that Objectify does not.
>> I just want to discourage the notion that you can easily work on
>> Appengine without using the Key class.  Judging by the official sample
>> code, this isn't even easy using JDO!
>>
>> 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.
>
>

-- 
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.

Reply via email to