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.