On Wed, Mar 10, 2010 at 7:39 PM, John Patterson <[email protected]> wrote:
> On 11 Mar 2010, at 03:40, Jeff Schnitzer wrote:
>
> That is an empty claim with no example or evidence.  Every comparison we
> have see so far is cleaner  and more readable in Twig.

Nonsense.  The only example that was "cleaner and more readable in
Twig" was a single and extremely contrived case based on a pattern
that nobody is actually using.  And even that is no longer prettier in
Twig.

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

Let's think for a minute why anyone cares if Key, Email, GeoPt, Link,
etc shows up in higher levels of an application:  portability.  Unless
you're working on Twig for other datastores, this entire line of
thought is moot.  If you use Twig for data access, you're stuck on
GAE.  If you use Objectify for data access, you're stuck on GAE.

The only data access API that gives you any hope (tenuous as it is) of
real portability is JDO/JPA.

Without portability, all this talk of "polluting" the higher tiers of
your application with datastore classes is a whole lot of religious
claptrap.

In my development with Objectify, I haven't found it necessary to use
Key objects in the higher levels of my app - just at the level of
DAOs.  However, I do use GeoPt a lot.  There are a lot of things that
might change if I had to port away from GAE, and this is just one of
them.

Sure, some data access frameworks can help portability more than
others, but if you are really concerned about moving off of GAE - you
want JPA.

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

Hmmm.  Except that "what is going on under the covers" in Twig
requires Twig, and Twig is not portable.  You seem to be making a
pretty good case for JDO/JPA.

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

The success of projects like Hibernate grew from the giant chasm
between objects and relational databases.  There were some solutions
that did more and some solutions that did less.  Hibernate did about
the right amount of stuff and had what for the time was a fairly
elegant API.

It's not always the case that doing more is better - JPA won out over
JDO.  Even hibernate ended up throwing out some major features (anyone
remember top level collections?)

The trick will be threading the needle between features and
complexity.  You weigh features more heavily, I weigh simplicity more
heavily.

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

Actually, it was quite confusing, and I'm not just being
argumentative.  Until you posted your delete example, it was not
apparent that your entity POJOs have lifecycle state; a POJO in an
entity graph might have been loaded from the database or it might be
empty, despite having a valid key.

Now it makes sense why you need a refresh() method.

I don't like the idea of entities that look like User #1234 but don't
have the data of User #1234.  This feels like it has a lot of bug
potential to me.

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

You may be the only one confused by this, probably because you didn't
read the documentation properly:  Query extends QueryResultIterable,
not QueryResultIterator.

And yes, you can call offset(5), then iterator(), then call
offset(10), and call iterator() again.  Each iterator will iterate
normally as you expect.

Once again, this allows queries to be elegantly run like this:

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

...which, while not being a major bullet point, is at least an mild
ergonomic win for Objectify.

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

Managing instance identity is not that hard.  It's a simple @Id field,
just like the @Id you are familiar with in JPA.  Maybe an additional
@Parent in the rare case you need one.  Not that complicated.

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

As someone who has read your documentation with more than just a
passing interest, let me offer this advice in the fullest of good
faith:  You need javadocs.  Really.

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

You're deluding yourself if you think people are more confused by the
Objectify API than the Twig API.  Say what you will about the feature
set but Objectify has the advantage of simplicity, and the query
interface follows the GAE/Python API fairly closely.  There is no
getting lost in a menagerie of Strategies and Commands.

> Person owner = datastore.load(Car.class, carKey).owner
> // now activate the inactive owner instance
> datastore.refresh(owner);

You need this example somewhere in your docs to explain that "empty"
POJOs get loaded, and how you convert them to "full" POJOs.  It wasn't
clear to me until I saw this example.

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

Yes yes, you implemented OR and in-memory sorting.  I don't personally
love these features.  With Objectify you must run two queries and sort
yourself.  I'm happy to concede this checkbox on the feature list.

It's possible that I just don't love your API.  Perhaps this is one of
those things that is cleaner in JPA-QL.

> And another:
> class Person
> {
> String name;
> Car car;
> }
> In Objectify, How do you store a collection of Person and their Cars??

I don't know what you are asking.  And frankly, I recoil in horror at
the absence of an id field on your Person entity.

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

Without dirty detection, aren't you likely to persist quite a large
object graph every time you do a put?

I have to ask something:  Why not just use JPA?  Or work on the
DataNucleus plugin?

I see the direction in which you're taking the project - and it's not
someplace that Objectify is likely to follow.  As impressive as
whatever you eventually come up with will be, it will be at least as
complicated as JPA and still be utterly tied to Appengine.

Philosophically, Objectify is more likely to head the other direction.
 Scott and I have bantered about the idea of creating a parallel
Objectify implementation for Cassandra - including a Key<?> class,
which makes quite a bit of sense there.  Now that would be *real*
portability.  For the moment it's just banter, but if I were going to
put as much effort into the API as you seem to be planning, this is
where I would go.

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

It seems pretty obvious to me, and I presume most programmers in the
world, that calling Query.iterator() will execute the query.  No big
warning necessary.

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

While you have indeed eliminated Key by using an empty POJO entity as
a surrogate, I hardly think your API is cleaner or more elegant.  I
know which of these I would rather type.

I also don't love the empty-POJO-as-surrogate concept.  User POJO
classes can contain all sorts of odd logic, and allowing some to sit
around in an uninitialized state is potentially quite dangerous.  It
seems rather easy for a developer to load EntityA (with an Activate(0)
field of type EntityB), make a change to what is actually an
uninitialized reference to EntityB, then try to save EntityB.  You'll
wipe out the contents of EntityB.  Do you prevent this from happening?

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

I looked at the documentation, and "extremely easy" is not the way I
would term it.  But fair enough, I'll concede "persistence of
arbitrary objects".

> Looks like the debate started early :)

Indeed :-)

Hey, I just noticed something.  Does Twig lack simple batch get() operations?

I frequently write code like this:

Set<Long> ids = ... // fetch user's friend ids
Collection<Person> friends = ofy.get(Person.class, ids).values();

What is the Twig equivalent?

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.

Reply via email to