On 11 Mar 2010, at 03:40, Jeff Schnitzer wrote:

On Wed, Mar 10, 2010 at 6:14 AM, John Patterson <[email protected]> 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).

No, even your "edge case" given below is solved just as efficiently. Details below

Yes, Activation gives you very precise control over what is loaded with the convenience of normal references.

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,

That is an empty claim with no example or evidence. Every comparison we have see so far is cleaner and more readable in Twig. Perhaps you could give an example from a "real application"

Readability is not even the most important issue here: Objectify model's dependence on low-level Keys means that *all* code that uses you data (the entire application) must also be dependent on the low- level datastore.

I notice that Objectify even encourages the use of low-level datastore classes on the client with its GWT module. Having my entire app dependent on the GAE environment, from data to browser, is not something I would be comfortable with when you realise how many projects are hitting "show-stoppers" late in the development and need to move platforms.

and easier to understand what is going on under the covers.

This "what is going on under the covers" in Twig is suddenly the responsibility of the developer with Objectify. As pointed out above, this makes Objectify applications less portable.

The success of projects like Hibernate grew from the fact that users don't want to deal with implementation details in their business logic.

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.

There are obviously situations where transactions are required

The parent reference is only needed in Objectify - Twig does not need the parent reference at all. This is in keeping with the design goal that persistent classes should have no restrictions.


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.

Oh come on! What is so difficult to understand here? Calling refresh(Object) gets the latest data from the datastore.

Objectify has, basically,
four methods:  get(), put(), delete(), and query().  They work exactly
the way you would expect.

I wouldn't say that - why does Query extend QueryResultIterator? This is not at all expected. I could call offset() on your "Query" in the middle of iterating through its results... the API seems to suggest that is possible. Quite weird.

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()?

Of course Twig has more API methods than Objectify - Objectify does not manage instance identity.

Twig seems to lack javadocs.

The code is generally a lot more descriptive and fluent which helps understanding more than depending on JavaDocs.

Take for example these Objectify methods: filter(), ancestor(), sort(), cursor(): Suer they succinct but there is no indication whether any of them add to the query state or replace it i.e. is more than on filter allowed?

A new developer would need to consult the JavaDocs for this. Twig makes this very clear with names such as addFilter(), withAncestor(), continueFrom(Cursor). Although you know the low-level API very well, don't expect new users to or maintenance workers in 2 years time.

However, I admit the JavaDocs have taken second place to performance and features in getting this release ready. That will be remedied in time.

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) {
   ...
}

        Person owner = datastore.load(Car.class, carKey).owner

        // now activate the inactive owner instance
        datastore.refresh(owner);

Now for Objectify: How do you implement my earlier example in Objectify?

"Find highly skilled .NET or Java programmers ordered by skill"

Twig:

Iterator<User> users = datastore.find()
        .type(UserSkill.class)
        .addSort("ability")
        .addFilter("ability", GREATER_THAN, 5)
        .addChildQuery()
                .addFilter("skill", EQUAL, "java")
        .addChildQuery()
                .addFilter("skill", EQUAL, ".net")
        .returnParentsNow();

And another:

class Person
{
        String name;
        Car car;
}

In Objectify, How do you store a collection of Person and their Cars??


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:

Both store() and update() cascade to every non-persisten instance and make them persistent. update() is called on every persistent instance that has been modified.

Automatic dirty detection is on the road map.

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.

Yes these types of cascade options will become necessary once automatic dirty detection is available. But non-bytecode-enhanced classes will always be an option.

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.

My point is that all code dealing with Keys is harder to read than direct references even if it is in a DAO. More importantly, the code which you pass Users into must also include Objectify code. The data layer is leaking into every other application layer.

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:

I see you've added that now. Looks like this discussion is having immediate effects on both.

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

 hiding the code inside a DAO does not mean it is not there

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);
}

Do you expect to have knowledge of every programming mistake Objectify users make? This is the type of programming problem that is very hard to detect until you look very carefully at why you code is running so slowly.

I would definitely not agree that this hidden gotcha is worth the ability to cram a query into a foreach loop. You should put a big warning in the docs:

"BEWARE: Every use of an Iterable executes the Query again!"


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());

I'll break this into separate statements just for clarity:

Iterator<Car> carsToDelete = datastore.find()
        .type(Car.class)
        .addFilter("weight", GREATER_THAN, 5000)
        .returnNoFields()               // does a keys only query
        getResultsNow();

datastore.deleteAll(carsToDelete);

Have any more examples or do you now accept that Keys are just not needed?


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.

Sorry, I misused Objectify terminology. But my point remains. Your claim that "The same can be said
for JDO or Objectify" is just FUD

 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.

Not at all. The docs make it quite clear that annotations are a completely optional way to configure a data model. Doing so in Java code is extremely easy and gives more control.

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

Cleaner is having the choice to use them when it makes sense. As the designer of Objectify Im not surprised you are ok with this.

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);

"real world" applications have relationships.

...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 hardly "keep coming back to" RIE's - I was duplicating your own example.

Looks like the debate started early :)

John


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