On Wed, Mar 10, 2010 at 6:14 AM, John Patterson <jdpatter...@gmail.com> wrote:
>
> No that is incorrect.  You actually have very fine control over what is
> loaded and when using "activation" [1]

Ah, this Activation annotation does help out.  But it still leaves
edge cases (read on).

I also don't think it really improves the the code as much as you
think it does.  In a real application (not a contrived one-line
example), I believe the Objectify code will be easier to write, easier
to read, and easier to understand what is going on under the covers.

And all this obsession with @Parent seems to me a bit odd, since in a
real application @Parent should be fairly rare.  Composite entity
groups inhibit performance and don't really buy you much.  Most
applications are better off with every entity as a root.

> Now the field contains an "unactivated" instance that does not require a
> fetch at all - the key is cached with a weak reference so when you want to
> activate the instance just call refresh(user).  Convenient and efficient.

I have to admit that you've lost me here.  Objectify has, basically,
four methods:  get(), put(), delete(), and query().  They work exactly
the way you would expect.

Twig adds quite a few API methods and the docs don't make it clear
what they do.  What are refresh(), update(), storeOrUpdate(),
associate(), and disassociate()?  Twig seems to lack javadocs.

So, let's take this Twig example:

class Car {
    @Key String key;
    @Activate(0) Person owner;
}

How do you implement this method?

public static Person getOwner(String carKey) {
    ...
}

> Does a put() on an entity cascade to its parent?
>
> Storing any instance cascades to every reachable instance irrespective of
> the relationship.  An instance can only be stored once so if the parent is
> already persistent it will not be stored again.

Would you explain this in detail?  I thought Twig doesn't do dirty
checking.  Given our User/UserSkill example, let's say you want to:

load a UserSkill
set the ability of UserSkill to 11
set the User's nickname to "Java Master"
save the entities

Does a save of the UserSkill cascade to the User?  Whatever the
answer, what do people who want the "other behavior" do?  There is a
reason JDO & JPA have all those cascade options.

> This is one of my gripes with the Objectify API - there is no distinction
> between "store" and "update" which makes it easy to accidentally clobber
> existing data.  The two actions are very different in intent and should be
> different methods.

You'll have to explain this one.  It seems extraordinarily difficult
to clobber existing data in Objectify.  There is only a put() method.
If you have an object with an id, it will save over the old object
with that id.  Null ids will produce a new generated id.  I can't
imagine what could be less confusing.

This is particularly handy with integer natural keys - if you make
your id value a long primitive type, it will never be generated.  If
you put entity with id 5, you save id 5.  Just like any other hash
map.

> Not only is the parent fetch efficient but it is much more readable.  Sorry
> Jeff, but this is just a train wreck:
>
> Objectify :
>
> List<Key<User>> userKeys = new ArrayList<Key<User>>();
> for (Key<UserSkill> key: ofy.query(UserSkill.class).filter("skill",
> "java").filter("ability >", 5).fetchKeys())
>    userKeys.add(key.getParent());
>
> Collection<User> users = ofy.get(userKeys).values();
>
> Do you really expect your maintenance developer to understand that in 2
> years time?  Im sure complex queries become even more convoluted.

This is absurd.  It's four lines of boilerplate that is easily wrapped
in a DAO to deal with a use case that doesn't seem to actually be all
that common.  The only reason we haven't added this trivial
convenience method to Query is because nobody has asked for it, and we
have a general policy of not bloating our API with methods that nobody
uses.

Nevertheless, since this triviality seems to come up on mailing list
discussions, we're probably going to add the fetchParents() method:

Iterable<User> users = ofy.query(UserSkill.class).filter("skill",
"java").filter("ability >", 5).fetchParents();

Even without this method, in the real world your code looks like this:

Iterable<User> users =
dao.getParents(ofy.query(UserSkill.class).filter("skill",
"java").filter("ability >", 5));

Hardly a "trainwreck".

> I notice you return an Iterable rather than an Iterator.  The problem with
> this is that every time the Iterable is accessed the query is executed
> again.  A little dangerous and non-obvious in my opinion.

This is another bit of FUD.  Nobody to my knowledge has yet made this
mistake.  And being Iterable makes queries *quite* elegant:

for (Car car: ofy.query(Car.class).filter("weight >", 5000)) {
    doSomethingWith(car);
}

> You will have to clarify what you mean by "except when you can't".  There is
> no reason to have any dependency on the datastore in a Twig data model.

Okay, how about this:  How do you delete instances without loading them first?

Give me your equivalent code:

ofy.delete(ofy.query(Car.class).filter("weight >", 5000).fetchKeys());

> Neither JDO or Objectify support direct unowned relationships which makes
> your models dependent on the datastore.  Also, Objectify requires every
> persistent instance to define a key - this means that no class can be
> persisted unless it is specifically designed to be a data model class.  The
> simple examples above show this nicely.

Objectify requires every class to define an *id*, not a Key.  And the
idea that you can take any class and make it a data model class is
pretty fanciful.  You might be able to get away with it in limited
cases, but in the real world you pretty quickly will start needing
@Activation, @Embed, or other annotations that control persistence.

Personally, I'm ok with all entities requiring an id.  It's... cleaner.

>> Incidentally, you can generally avoid Key references using Objectify
>> if you so choose.  Keys only become mandatory when you are dealing
>> with parented entities -- which should be used infrequently for
>> performance reasons.  There's no reason you can't just use simple ids
>> as foreign key references.
>
> That is a much worse solution than using Keys even!  You really expect
> developers to translate long ids into Keys and then look them up???  I think
> this is getting a little ridiculous.

Objectify users do not work with Key objects as often as you seem to
think.  To look up a car with id 5, you simply call

Car car = ofy.get(Car.class, 5);

...and you can pretty easily navigate object graphs with stored ids
that way.  If that's your bag.

> Iterator<User> users = datastore.find()
>        .type(UserSkill.class)
>        .addFilter("skill", EQUAL, "java")
>        .addFilter("ability", GREATER_THAN, 5)
>        .returnParentsNow();
>
> This is how easy it is to use Relation Index Entities with the new release
> of Twig

You keep coming back to this Relation Index Entities pattern.  I am
going to stand on this claim until someone, *anyone* comes up with a
real-world use case that proves me wrong:

 * The Relation Index Entities pattern is useless in the real world.

I will cheerfully concede the point as soon as someone points at some
feature of some existing website that would be appropriately
implemented this way.  Here's a hint, it's certainly not Twitter or
any other service that Slatkin talked about.

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.

Reply via email to