On 11 Mar 2010, at 16:27, Jeff Schnitzer wrote:

On Wed, Mar 10, 2010 at 7:39 PM, John Patterson <jdpatter...@gmail.com> 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.

Well then, where are your examples? I responded to the "edge cases" you asked about with a simple example. Now its your turn to show me how Objectify would code the example I asked about.

So wheres the code? Where is this clean Objectify code you claim is possible? The question is still waiting below

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.

You are still mixing the concepts of data model portability and data access layer (DAOs) portability.

Of course the code to actually manipulate data uses a particular interface and is dependent on that interface. The only persistence framework I am aware of that approaches complete transparency is Terracotta.

I never claimed that Twig code would run on other platforms. What I claimed was that the *data models* are not dependent on the datastore. Generally a lot of application code simply uses data without needing to persist changes - especially in webapps. All of this code can remain portable ... unless your data models are not portable.

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

Are you having a laugh? After all your rants about how portability with JDO-GAE is impossible... that is really grasping at straws.

I claim that Twig data models are more portable that JDO-GAE, JPA and Objectify data models. Why? Because they are pure POJOs with no low level datastore dependencies - simple. Yes you can run JDO on other platforms - but

I do not claim that the data access layer is portable - although certainly neither is Objectify

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

It is not important until you hit one of the many "show-stoppers" that these mailing lists are full of. Then it becomes a little more interesting.

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.

Obviously a rather large one.

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

In a system such as GAE which makes so much so hard productivity features are a great relief. No OR queries? Of course I value simplicity - but the whole idea of a framework is to push complexity from the user code into the framework. Objectify leaves too much too the developer for my liking. It is simple - I grant you that - but that makes app code more complex.


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.

It is covered in the docs. Activation is a central concept with Twig - modelled on Db4o.

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.

All referenced objects are activated by default - the developer actually has to explicitly choose to use this feature as an optimisation so there are no surprises.

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.

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.

Cramming a query into a for loop is hardly a gain worth risking programming errors over.

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.

Managing object identity is a lot more than just keeping an Id field. It is a guarantee that if you load the same instance twice from the datastore they will be identical instances. obj1 == obj2

Objectify has no such guarantee so you could end up loading two referenced instances with the same id, making changes and puting them again only for one to overwrite the other.


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.

The Objectify framework has the advantage of simplicity because it has fewer features. The Objectify user code is more complex because the user must do more "manually". The merged OR query is just one example of this.

In my opinion the goal for any GAE framework should be to help overcome the great and unusual limitations of the environment. To encapsulate solutions in one place to avoid repeating them again and again.

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.

Good point.  I'll add some more examples.


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.

I would not call this "in-memory sorting" which implies that the objects are loaded and then sorted. The way it is implemented merges the already sorted results as a _stream_ into a single iterator.

With Objectify you must run two queries and sort
yourself.  I'm happy to concede this checkbox on the feature list.


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?

No. Updating or storing an item makes all reachable items persistent _only_ if it is not already persistent. Each item that changes must have update called.

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

Twig is already much more capable than JPA on GAE for a very good reason: It is designed specifically to work with GAE. Integrating performance features like "Parallel Asynchronous Commands" into JPA or JDO would be impossible.

I can honestly say that without Twig my app would be at least 100 times slower. Why? Embedded collections have probably reduced the multiple queries required in JDO by 10 fold. Parallel queries have now reduced the query another 10 fold.

In fact a complex geo-spatial query that now runs in 50ms did not even return in 30 seconds using my first approach with JDO.

Enough said.

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.

Specific to App Engine, yes. As complicated as JPA, no. The standard interface implementations have much more to contend with because they must follow a spec.

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 -

who's that? Your girlfriend? Ha ha I just looked at the project page... very interesting.

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.

I have also considered how Twig could be made to run on other platforms... but there the danger is taking a lowest common denominator approach and ending up like JDO/JPA. Twig was started to solve problems that were not possible before. Now its time to put that to work.

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.

Reducing key strokes is a much lower priority in Twig than creating readable, maintainable code.

It seems that method naming in Objectify takes the oposite approach.

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?

This would be handled with a simple direct collection reference in Twig like this:

user.friends

So the batch get by key is really not so useful in Twig - or at least I haven't had the need for it yet. But I can see cases where would be useful and am planning on adding a fluent load (and delete) commands which - just like the find and store commands - has "advanced" features such as returnResultsLater() and bulk load.

I just noticed, how do you set the chunk size in Objectify? I can't see the option anywhere.

In Twig it is getResultsBy(50)

Now, back to work 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 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