Jon
Missplet on my ipone
1. using oft.find() for gets doesn't make it clear when you're doing a
get and when you're doing a query - which is quite important to know
because of the underlying datastore implementation speeds / cost.
2. If you're coding in your IDE and think I'd like to do z, x and y -
is there anything to stop you getting the methods in the wrong order?
Is there a wrong order? For example can I do
ofy.find().id(123L).type(Thing.class);
instead of
ofy.find().type(Thing.class).id(123L);
or
ofy.now().put().entities(e1, e2, e3);
instaead of
ofy.put().entities(e1, e2, e3).now();
and how do I know which re-orderings are valid and which are not?
3. What are get() and getSafe() and how are they different? Does get()
return a runtime exception instead of a checked exception for "not
found"? Is NotFoundException a checked exception now?
4. Will the new version continue to use a CachingDatastoreService that
can be used outside of Objectify - I think this is really useful and
also less likely to be updated (for improvements - I'm sure bug fixes
will still happen) if it's not used in the main code. Maybe it would
only have a async version now - that would be OK I guess?
I really like that properties will be unindexed by default and session
cache being enabled by default. And using the Map interface to hide
asynchronism is a great idea. I think I prefer "@Indexed" to "@Index"
on a field because to me it's a description of the field rather than
an instruction.
These are just my thoughts, some of them are probably just me not
understanding fully.
Thanks,
Mat.
I'm not sure this is entirely true. I thought that the session cache
was not used for transactions. Presumably it would be possible for
Objectify to create a new object for each call to get() regardless of
whether the session cache is used - although it does complicate the
cache a bit but I think it would then be possible to make it a
"per-JVM" cache instead of "per-Session" based?
Hi Jeff,
I have to say that I'm not very sure about the new API (but maybe
it's just in a style I'm not used to) - I really like the existing
one. Things that occur to me are:
1. using oft.find() for gets doesn't make it clear when you're doing a
get and when you're doing a query - which is quite important to know
because of the underlying datastore implementation speeds / cost.
2. If you're coding in your IDE and think I'd like to do z, x and y -
is there anything to stop you getting the methods in the wrong order?
Is there a wrong order? For example can I do
ofy.find().id(123L).type(Thing.class);
instead of
ofy.find().type(Thing.class).id(123L);
or
ofy.now().put().entities(e1, e2, e3);
instaead of
ofy.put().entities(e1, e2, e3).now();
and how do I know which re-orderings are valid and which are not?
3. What are get() and getSafe() and how are they different? Does get()
return a runtime exception instead of a checked exception for "not
found"? Is NotFoundException a checked exception now?
4. Will the new version continue to use a CachingDatastoreService that
can be used outside of Objectify - I think this is really useful and
also less likely to be updated (for improvements - I'm sure bug fixes
will still happen) if it's not used in the main code. Maybe it would
only have a async version now - that would be OK I guess?
I really like that properties will be unindexed by default and session
cache being enabled by default. And using the Map interface to hide
asynchronism is a great idea. I think I prefer "@Indexed" to "@Index"
on a field because to me it's a description of the field rather than
an instruction.
Hi, my main concern is, will all these still work with gwt-rpc?
I like the idea of making changing the read policy easier - I did use
the eventual consistency before moving to the CachedDatastoreService -
I think caching will speed up my gets more for now.
I see what you mean about @Inject - it's pretty standard usage so I
guess it's better to go with the rest of the Java community!
Thanks again for all your work. If I ever get the confidence to quit
my job and start my business I'll have less work to do on the
datastore :-D
Mat.
Hi Jeff,I'm not very convinced of the direction you want to give Objectify.
I like its simplicity, its resemblance to the low-level API and the ability to create POJOs compatible with GWT-RPC. I like that the Keys are always visible because it is in this way that the datastore works.
In my opinion, what is lacking is a more powerful lifecycle callbacks, which probably allows you to create automatic fetches relationship, without polluting the current syntax.
I chose to use Objectify, rather than Twig because I like how Objectify works, otherwise I would have chosen Twig.
About the Session Cache, I don't like it. Breaks the transaction isolation and introduces the possibility of side-effects, since it works with the same object instance.
This is my personal opinion.
A user of Objectify since version 1
"About the Session Cache, I don't like it. Breaks the transactionI'm not sure this is entirely true. I thought that the session cache
isolation and introduces the possibility of side-effects, since it
works with the same object instance."
was not used for transactions. Presumably it would be possible for
Objectify to create a new object for each call to get() regardless of
whether the session cache is used - although it does complicate the
cache a bit but I think it would then be possible to make it a
"per-JVM" cache instead of "per-Session" based?
* Putting Key<?> in the data model is a PITA sometimes
Not sure what this really means.
* Hard to create tidy data models for jsonification or serialization
to clients; tends to require ugly @Transient fields
Long ago I have decided never to mix "client" and "backend" code in
the same classes. I have 2 sets of classes, one for UI/JSON and the
other for persistence, each being very simple as it only serves one
purpose. People dislike this approach as it requires more code, but I
argue that the extra code is simple code, easy to maintain. Mixing
purposes, on the other hand, quickly leads to convoluted code, even if
it's more compact in terms of LoC.
* Reliance on JPA annotations is problem for some
Is it because the required JPA JAR fattens up our apps?
* Session cache enabled by default
Not sure if I like this. Correct me if I'm wrong, but there's a low-
ish memory limit for each application instance, isn't there? The
quicker an app instance reaches this limit, the more frequently GAE
has to replace a bloated app instance with a fresh one, which would
lead to more users experiencing a cold start, I assume.
@Fetch({"bigGroup", "smallGroup"})
SomeThing some;
@Fetch
Ref<OtherThing> refToOtherThing;
From design goals: Use Ref<?> when asynchrony cannot be hidden (ie,
returning concrete classes)
I don't get it?! What's the difference between SomeThing & OtherThing here?
Is it a big step further to do 'put-groups'? just to make life easier
when saving an object graph a few levels deep. Perhaps with a
lifecycle callback for dirty detection.
Also, a hook in entity creation to enable injection would be a welcome
feature! A lot of my entities have instance methods with logic, and
need to be injected with services at times.
The difference between using Ref<MyThing> and just MyThing isn't
really clear to me though:
@Fetch({"bigGroup", "smallGroup"})
SomeThing some;
@Fetch
Ref<OtherThing> refToOtherThing;
From design goals: Use Ref<?> when asynchrony cannot be hidden (ie,
returning concrete classes)
I don't get it?! What's the difference between SomeThing & OtherThing here?
Is it a big step further to do 'put-groups'? just to make life easier
when saving an object graph a few levels deep. Perhaps with a
lifecycle callback for dirty detection.
Also, a hook in entity creation to enable injection would be a welcome
feature! A lot of my entities have instance methods with logic, and
need to be injected with services at times.
boolean changed = false;changed = changed |= obj.setThing1(foo);changed = changed |= obj.setThing2(bar);if (changed)ofy.put(obj);
Hey Jeff,
Most of the changes I personally like.
The only question I have is why are you making Query immutable?
There are certain use cases (REST Query api with params that are not
required...) where have the query object mutable is a great asset.
Option A has my vote. The only issue with @Ignore is that it doesn't specify what will ignore it - Another developer? Objectify? The user? Some other persistence system? Standard java serialisation?
I just re-looked at Morphia's annotations and the one major difference is to use @Reference instead of @Fetch. I have mixed feelings about that... the nice thing about @Fetch is that it parallels the ofy().find().fetch("groupName") method. Although it could be ofy().find().reference("groupName"). I would certainly be ok with keeping the @Indexed @Embedded etc annotations and using @Reference instead of @Fetch. Anyone want to vote on this? Option A: @Index @Unindex @Ignore (maybe instead of @Transient?) @IgnoreSave (maybe instead of @NotSaved?) @Fetch (combined with ofy().find().fetch("groupName").etc...) @AlsoLoad @Cache @Serialize @Embed Option B: @Indexed @Unindexed @Ignored (maybe instead of @Transient?) @NotSaved @Reference (combined with ofy().find().reference("groupName").etc...) (or maybe use @Fetch) @AlsoLoad (forget @AlsoLoaded, that sucks) @Cached @Serialized @Embedded |
Like it.
Where is caching fitting in with this API?
Obviously the id and ids results are cached within the global cache.
Will anything else be in the global cache?
I remember talk earlier of every query being keys only and then using
batch gets... is this still in the pipeline or have new ideas emerged?
Option B: Require an explicit instruction on the Query interface. Probably something like: ofy.load().filter("foo", foo).hybrid().entities()
ObjectifyService.register( YourEntity.class ).withId( "vin" );
ofy.load().entity(thingKey)seems funny and also not consistent with this (which I like):
Perhaps the first should readofy.load().type(Thing.class).id(123L)
to be more consistent. The other fluent methods are naming what is passed in or what state they are changingofy.load().key(thingK ey);
Iterable<Thing> ths = ofy.load().group("group").type(Thing.class).filter("foo", foo).entities();I would prefer ".instances()" or something other than "entities()"
if (clazz.equals(MyClass.class))Actually there are probably easier ways to create annotations by defining them directly like
{
// create annotations programatically
return new SomeObjectifyAnnotation()
{
public int someMethod()
{
return 5;
}
};
}
1. using oft.find() for gets doesn't make it clear when you're doing a get and when you're doing a query - which is quite important to know because of the underlying datastore implementation speeds / cost.
Return null when an instance is not found? To me it doesn't really seem like a "exceptional" circumstance if you check for an instance and it is not there.2. If you're coding in your IDE and think I'd like to do z, x and y - is there anything to stop you getting the methods in the wrong order? Is there a wrong order? For example can I do ofy.find().id(123L).type(Thing.class); instead of ofy.find().type(Thing.class).id(123L); or ofy.now().put().entities(e1, e2, e3); instaead of ofy.put().entities(e1, e2, e3).now(); and how do I know which re-orderings are valid and which are not? 3. What are get() and getSafe() and how are they different? Does get() return a runtime exception instead of a checked exception for "not found"? Is NotFoundException a checked exception now?
I really like that properties will be unindexed by default
and session cache being enabled by default.
.unique() which throws an exception if there is more than one result (very useful sanity check!) and .all()
Foo foo = ofy.find().type(Foo.class).id(123).get();vsFoo foo = ofy.get(Foo.class, 123);
But it makes all the other cases simpler, dramatically reduces the method count on Objectify, and stops me from having to maintain parallel methods on Objectify and AsyncObjectify. Truth be told, you could easily add the above get(Class, long) method to a wrapper for your own convenience.
Dirty change detection is interesting but that is also a massive step with major API implications. The problem with dirty change detection is that it requires an explicit boundary - you have to open a session and close it, at which point changes are synced. This ends up being a try/finally pattern which gets ugly.
On the other hand, there are definitely times I have wanted dirty change detection, and end up faking it with methods like this:
boolean changed = false;changed = changed |= obj.setThing1(foo);changed = changed |= obj.setThing2(bar);if (changed)ofy.put(obj);
It's not elegant. I'm not sure what the answer is but maybe sometime in the future we will come up with a way to address this that doesn't require everyone to define try/finally boundaries. Objectify 5.0.
Also, a hook in entity creation to enable injection would be a welcome
feature! A lot of my entities have instance methods with logic, and
need to be injected with services at times.
This is an exceptionally good idea. I will delegate object creation to an overridable method on ObjectifyFactory so you can delegate to Guice or whatnot.
Also, a hook in entity creation to enable injection would be a welcome
feature! A lot of my entities have instance methods with logic, and
need to be injected with services at times.
This is an exceptionally good idea. I will delegate object creation to an overridable method on ObjectifyFactory so you can delegate to Guice or whatnot.
I've also used this override to create Guice interceptors that do auto-activation. So as soon as you call certain methods on a model that has not been activated, it goes and gets the data for you.
Option D: leave it to the developer?
ofy.load().keys(ofy.find().filter("foo", foo).keys())
It is explicit and removes magic
Option D: leave it to the developer?
Hi Guys, just some questions and points from reading the through current design. I love the direction and the new innovation with features like fetch groups which should really help performance.
I am very used to doing similar things the Twig way - so just to throw the cat among the pigeons and hopefully create a bit of debate before the API gets too set in stone....
Using the word "entity" in the API makes me think of the datastore Entity class. I prefer to use "persistent instance"or "instance" in the API to remove any confusion with an Entity stored in the datastore. "Instances" are Java objects with fields and Entities are bags of properties or a representation in Big Table. So:
ofy.load().entity(thingKey)seems funny and also not consistent with this (which I like):
Perhaps the first should readofy.load().type(Thing.class).id(123L)
ofy.load().key(thingK ey);
to be more consistent. The other fluent methods are naming what is passed in or what state they are changing
The word "entity" is also doing double duty when used as a terminator to the chain:
Iterable<Thing> ths = ofy.load().group("group").type(Thing.class).filter("foo", foo).entities();I would prefer ".instances()" or something other than "entities()"
I notice that the return type from queries is Iterable<T> which is a good default. I assume it is backed by the "live" QueryResultIterator so results are fetched in batches. Also, there is first() which is great.
In Twig, I also frequently use .unique() which throws an exception if there is more than one result (very useful sanity check!) and .all() which loads every result in a single datastore API call by setting the chunk size and fetch size to MAX_VALUE. Very useful when you know you are not dealing with thousands of results.
all() returns a List rather than an Iterable because it has the complete result set in memory.
On 14/11/2011 16:01, Matthew Jaggard wrote:I also like the difference to be clear and worry about the correct choice being made by the framework.1. using oft.find() for gets doesn't make it clear when you're doing a get and when you're doing a query - which is quite important to know because of the underlying datastore implementation speeds / cost.
I'm don't understand yet though, how this choice would be made. Still, it really seems like something that shouldn't be hidden from the user? Open to being convinced though
Return null when an instance is not found? To me it doesn't really seem like a "exceptional" circumstance if you check for an instance and it is not there.3. What are get() and getSafe() and how are they different? Does get() return a runtime exception instead of a checked exception for "not found"? Is NotFoundException a checked exception now?
I'm still not really sure how direct references can work without the session cache being enabled. How would circular references work?and session cache being enabled by default.
class Band
{
List<Musician> members;
}
class Musician
{
List<Band> bands;
}
without a session cache this circular reference would keep creating new instances?
I don't think it should even be an option to disable the cache. Is it only memory requirements that make this an issue? In Twig there is .disassociate(Object) which removes an instance from the cache if you are reading a lot of data and mem might be an issue. There was something similar in Hibernate if I remember correctly.
Also, the cached instances are held by SoftReferences so if you do not keep a reference to the instance in your program then it can be automatically removed from the cache if memory is low. Personally, with caching working like this, I have never seen a problem from memory usage.
I'm slowly getting my head around using the intrinsic interfaces like List, Map, Iterable to hide the async stuff. So the chain terminators should be and able to return different collection types or Ref.
.entities or .instances() is not consistent with .all() or .unique() or .first()
I was thinking perhaps it might be even more explicit to have terminators: .list() .iterable() .ref()
Thoughts? Is that what Objectify 3 uses? Sorry if I'm playing catch-up here!
BWT, I might be alone on this one but... why Ref instead of Reference? Its doesn't really save typing with with auto-complete. I've read similar discussions between Ceylon and Scala advocates and notice that Scala gets a bit of grief about having short forms of keywords that some people just don't like to look at. I'm generally in that group - there is not much logic to it! I just like t read full, complete words :) At the end of the day its no big deal
I tend to dislike abbreviations too but for things I type a lot, I like shorter names as long as they are not ambiguous. Ref is almost a word by itself - people talk about refs, ref counting, etc in programming context all the time. Also, a three-letter Ref<?> is very similar to Key<?>, which is good because they are closely related.
Twig has such a "short cut" method .load(Class<?>, Object id) and I use if often. Also, its counterparts .store(Object, String) and .store(Object, long)
It could be good to store this "dirty" state in the Session Cache. Also, its good to store activation state for each instance. Before Twig did this, it was very easy to accidentally put an unactivated instance in the datastore. After introducing activation state tracking, this became impossible (exception thrown) and also you would not activate the same instance more than once.Dirty change detection is interesting but that is also a massive step with major API implications. The problem with dirty change detection is that it requires an explicit boundary - you have to open a session and close it, at which point changes are synced. This ends up being a try/finally pattern which gets ugly.
On the other hand, there are definitely times I have wanted dirty change detection, and end up faking it with methods like this:
boolean changed = false;changed = changed |= obj.setThing1(foo);changed = changed |= obj.setThing2(bar);if (changed)ofy.put(obj);
It's not elegant. I'm not sure what the answer is but maybe sometime in the future we will come up with a way to address this that doesn't require everyone to define try/finally boundaries. Objectify 5.0.
I've also used this override to create Guice interceptors that do auto-activation. So as soon as you call certain methods on a model that has not been activated, it goes and gets the data for you.
For auto-activation it is also required to track activation state. There is a method ObectDatastore.isActivated() which is used by the proxy to check if it needs to activate or not.
I considered storing state in the instance itself (dirty state, activation state) using cglib or AspectJ but that would mess with serialization.
So the SessionCache ends up needing a bi-directional mapping - Key to Instance and Instance to Key (actually an Object holding Key and other state).
I'm making a lot of assumptions here about Objectify internals that I really don't know!
On Fri, Nov 18, 2011 at 9:10 AM, John Patterson <jdpat...@gmail.com> wrote:I fear that "persistent instance" is very JDO-ish, and "instance" is totally ambiguous (instance of what?). For better or worse, we've used the term Entity everywhere in the documentation already and there is a direct 1-to-1 correspondance between an Objectify entity and a low-level entity. Plus, I suspect most people who come to Objectify aren't intimately familiar with the low-level api and don't want to be. So I'm not sure it makes sense to have a linguistic distinction between the kinds of entities any more than there is between the kinds of keys.
As far as ofy().load().entity(key) vs ofy().load().key(key), you're right this is a bit awkward. In fact, a previous iteration of API proposal had methods like this (showing return and param values):
Ref ofy.load().key(Key<?>);Map ofy.load().keys(Iterator<Key>);Map ofy.load().keys(Key<?>...);
Ref ofy.load().rawKey(Key);Map ofy.load().rawKeys(Iterator<Key>);Map ofy.load().rawKeys(Key...);
Ref ofy.load().entity(Object);Map ofy.load().entities(Iterator<Object>);Map ofy.load().entities(Object...);
Map ofy.load().values(Iterator<Object>); // can be heterogenousMap ofy.load().values(Object...);
I am not fond of this:
1) It's a lot of methods, and there's a lot of redundancy. For example, entities() and values() are basically the same method - you can pass in anything. Is it really necessary to have separate keys() vs rawKeys()? It turns out all cases are handled by having methods entity()/entities() (or value()/values()).
2) There's an inconsistency with the way queries work. The query mechanism has methods keys() which means "return Key<?> objects" and entities() which means "return entity objects". Whereas this API has method keys(...) which means "pass in a key" and entities() which means "pass in entity objects".
Of course, this is incongruent with both type().id() as well. And maybe the query API is the part that should be "fixed". And maybe it is. But it's hard to see the value of having 11 methods when 3 will suffice with the exact same syntactic structure.
Maybe the answer is that instead of condensing to entity()/entities(), we should condense to key()/keys() (which takes Object as a parameter so you can pass in an entity). It's a little odd to pass in an entity as a key but that's what we're proposing anyways - entities are key-like structures now.
I would prefer ".instances()" or something other than "entities()"
Or we could just drop it and stick with iterable(). It's present already:
Iterable<Thing> ths = ofy.load().group("group").type(Thing.class).filter("foo", foo").iterable();
Of course this makes it easy to do stuff like this, which I find I use now and then:
for (Thing th: ofy.load().type(Thing.class).filter("foo", foo)) {...}
More comments about the query API in a later response.I notice that the return type from queries is Iterable<T> which is a good default. I assume it is backed by the "live" QueryResultIterator so results are fetched in batches. Also, there is first() which is great.
The actual return value is QueryResultIterable and QueryResultIterator so you can get the cursor(). I was just being a lazy typist in the design doc. The current java code is correct in this regard.In Twig, I also frequently use .unique() which throws an exception if there is more than one result (very useful sanity check!) and .all() which loads every result in a single datastore API call by setting the chunk size and fetch size to MAX_VALUE. Very useful when you know you are not dealing with thousands of results.
Sure, we can definitely add .unique() which adds the cost of an extra fetch (limit(2) instead of limit(1).
The all() method is analogous to list(). I don't have a strong opinion on the naming convention.
all() returns a List rather than an Iterable because it has the complete result set in memory.
There's actually some interesting behavior in the low-level api's List (which we don't currently use). It will actually cursor through the results. I don't think this is very good behavior because it tends to be slower than fetching the list all at once unless you're doing expensive operations at each iteration - in fact, you can speed up the app by calling size() on the list before iterating.
The way I made this distinction in Twig by naming all the "chain
terminators" with returnXXX. So .returnAll() .returnUnique() .returnCount()
I *think* AspectJ can do field interception. CGLIB creates a subclass
so that does not help. You could certainly do it with ASM but I don't
think in bits and bytes... it is so low level.
There is small project, Salve, by Igor of the Wicket project
http://code.google.com/p/salve/wiki/WhyNotAspectJ
Which does very clever stuff with ASM and I think it has its own utils
to make ASM easier to use.
Opps let not get into this!
On 19/11/2011 20:18, Jeff Schnitzer wrote:
> Just a random thought on the query api before I run out the door...
> what about using all() to signify the start of a query chain, kind of
> like the way gae/python does it?
all() should work consistently with unique() first() count() yeah?
perhaps I'm too tired to see how that can work
On 19/11/2011 20:18, Jeff Schnitzer wrote:
> Just a random thought on the query api before I run out the door...
> what about using all() to signify the start of a query chain, kind of
> like the way gae/python does it?
all() should work consistently with unique() first() count() yeah?
perhaps I'm too tired to see how that can work
Looking at these terminator methods together it is clear that all() fits
better than list()
I would go even further and suggest it should always be on (cannot
disable) and items have to be explicitly removed from the cache. If
half of users were doing things one way, and half the other way, many
user questions on this list would be responded to with "Do you have
session caching enabled or disabled?". Perhaps to enforce best
practices from the start.
Actually Jeff, would you even need the load() keyword then?
ofy.type(Thing.class).all().filter("foo", foo)
ofy.type(Thing.class).delete(thingId);
ofy.type(Thing.class).deleteAll(); //ow!
Actually Jeff, would you even need the load() keyword then?
ofy.type(Thing.class).all().filter("foo", foo)
ofy.type(Thing.class).delete(thingId);
ofy.type(Thing.class).deleteAll(); //ow!
I think of loading a new entity by key or id and adding it to the
session as different to filling in the field values of an existing
entity that already exists in memory (activating it). Activating an
entity does not need to return a value like load() does - the fields are
just filled in on the existing entity instance. So its quite a
different beast.
> The current proposal (which I am not in love with either):
>
> 1) QueryResultIterable<Key<?>> things = ofy.load().keys(); //
> implicitly keysOnly().keys()
> 2) QueryResultIterable<Object> things = ofy.load().keysOnly().iterable();
> 3) Key<Object> thingKey = ofy.load().keysOnly().first().key(); // or
> unique().key();
> 4) Object thing = ofy.load().keysOnly().first().get(); // or
> unique().get();
> 5) Object thing = ofy.load().first().get(); // or unique().get();
>
> Some things that can be done to make this simpler:
>
> * Eliminate unique(), keep first() since first is cheaper. Forces
> users to perform unique checking themselves if they want it.
> Personally, I make sure that unique things are actually stored
> uniquely - I don't worry about detecting illegal duplicates because
> they can't happen.
It is still very reassuring to be certain that in your running system,
the data has not somehow become inconsistent.
We could share the implementation of unique() and first().
> - This allows us to use ref() instead of first() but honestly I
> think first() is more expressive.
yes
> * Don't make it possible to query for partial entities. Seems like
> this is missing some utility.
I use that a lot in Twig so would be good to keep. Also, it would allow
a keys only query for entities and then to selectively activate (load)
the unactivated instances. This could be really handy
> Here's an alternative:
>
> 1) QueryResultIterable<Key<?>> things = ofy.load().keys().iterableKeys();
> 2) QueryResultIterable<Object> things = ofy.load().keys().iterable();
> 3) Key<Object> thingKey = ofy.load().keys().first().key(); // or
> unique().key();
> 4) Object thing = ofy.load().keys().first().get(); // or unique().get();
> 5) Object thing = ofy.load().first().get(); // or unique().get();
>
> Or maybe invert the first two:
>
> 1) QueryResultIterable<Key<?>> things = ofy.load().keys().iterable();
> 2) QueryResultIterable<Object> things =
> ofy.load().keys().iterableEntities(); // iterablePartials()?
> entityIteratable()? entities()?
> 3) Key<Object> thingKey = ofy.load().keys().first().key(); // or
> unique().key();
> 4) Object thing = ofy.load().keys().first().get(); // or unique().get();
> 5) Object thing = ofy.load().first().get(); // or unique().get();
I want to spend some time to think about this a bit more. I guess you
are pushing on with the impl though.
> I tend to dislike abbreviations too but for things I type a lot, I
> like shorter names as long as they are not ambiguous. Ref is almost a
> word by itself - people talk about refs, ref counting, etc in
> programming context all the time. Also, a three-letter Ref<?> is very
> similar to Key<?>, which is good because they are closely related.
>
> I've had many arguments with Gavin about wordiness from the days
> before Ceylon had a name. Generally I like the verbose format but
> I've tried to convince him to shorten Integer to Int and value to val,
> just because they get typed so much they are special cases. So far he
> remains unconvinced.
Haha! I guess its simply a matter of preference and really there is
probably no best way.
I would definitely be with Gavin on that (really looking forward to
Ceylon!) but its 6 of one, half a dozen of the other. But let it be
duly noted that my preference is with Reference<T> and other full
words. Actually before Guice, I used PicoContainer which heavily used
ObjectReference<T>... you would despise that!
What do others think? Are many others still reading? Which do you
prefer to read/type? Anyway, its your call at the end of the day.
> BTW, this is interesting reading:
> http://www.quora.com/Java-programming-language/Why-do-some-people-hate-Java
>
> I tend to agree that one of the big problems with Java is verbosity.
> Too much getThis() and setThat() and catch
> (SomeIdioticCheckedException idontcareabout). I really enjoy working
> with python and javascript, but I've found that projects are too hard
> to scale up without static typing. I'm hoping Ceylon will be a sweet
> spot, even if I have to type Integer a lot.
Amen. I am an auto-complete junky even when it is not necessary so I
don't really just type identifiers anyway.
On 19/11/2011 01:14, Jeff Schnitzer wrote:I think of loading a new entity by key or id and adding it to the session as different to filling in the field values of an existing entity that already exists in memory (activating it). Activating an entity does not need to return a value like load() does - the fields are just filled in on the existing entity instance. So its quite a different beast.
Also - need better terminology for "unactivated entity" since I think we want to stick with @Load instead of @Activate. "unfetched entity"? "partial entity"? "empty entity"? "unloaded entity"? That last one makes the most sense if we are using the world load everywhere, but it's a little awkward. "Partial" kinda makes sense, especially in a world with @Denormalize. I'll use it here just for the hell of it.
On Sun, Nov 20, 2011 at 4:29 AM, John Patterson <jdpat...@gmail.com> wrote:
Actually Jeff, would you even need the load() keyword then?
ofy.type(Thing.class).all().filter("foo", foo)
ofy.type(Thing.class).delete(thingId);
ofy.type(Thing.class).deleteAll(); //ow!
It could be done this way, but remember that there are typeless queries and also the group() method. It would mean that the Objectify interface would extend Query<Object>, which would mean a long confusing list when eclipse tries to complete "ofy<dot>". Also, group() doesn't apply to put() or delete() (although it might in the future).
I would definitely be with Gavin on that (really looking forward to Ceylon!) but its 6 of one, half a dozen of the other. But let it be duly noted that my preference is with Reference<T> and other full words. Actually before Guice, I used PicoContainer which heavily used ObjectReference<T>... you would despise that!
What do others think? Are many others still reading? Which do you prefer to read/type? Anyway, its your call at the end of the day.
Firstly, as I have not actually tried to code any of this or read your code I'm likely to make stupid suggestions...
Objectify should not extend Query , for sure.
The above could be designed without group() applying to put() and delete(), Objectify not implementing Query and still keep your ObjectifyWrapper stuff. I've left out the type parameters that refer to the implementation class.
put() would return PutCommand or something like that.
type(...) would return some kind of TypedCommand<T> which exposes all() and first() etc
all() would return QueryTypedCommand<List<T>>
first() would return QueryTypedCommand<Ref<T>>
count() would would return QueryTypedCommand<Integer>
// Simple key fetch, always async
Thing th = ofy.load().key(thingKey).get();
Key<Thing> thKey = Key.create(Thing.class, id);
Thing th = Ref.create(Key.create(Thing.class, id));
Key<Thing> key = Key.create(Thing.class, id);
Thing th = ofy.load().key(key).get();
// Simple key fetch, always async
Ref<Thing> th = ofy.load().key(thingKey);
Thing th = ofy.load().key(thingKey).get();
Thing th = ofy.load().key(thingKey).safeGet(); // throws NotFoundException
That document is slightly out of date, but cutting out the context hurts the meaning:// Simple key fetch, always async
Ref<Thing> th = ofy.load().key(thingKey);
Thing th = ofy.load().key(thingKey).get();
Thing th = ofy.load().key(thingKey).safeGet(); // throws NotFoundException
Ref.create(Key.create(Thing.class, id));
@Load({"bigGroup", "smallGroup"})
SomeThing some;
@Load("bigGroup")
List<OtherThing> others;
@Load
Ref<OtherThing> refToOtherThing;
Ref<OtherThing> anotherRef; // no @Load means never fetched automatically
What do I get if I have "SomeThing some" without "@Load"? Is that even possible? And what's the difference between a Ref with or without "@Load". The Ref with "@Load" already contains the OtherThing so I can get() it without contacting the database again? |
Without @Load entity won't be automatically loaded when you fetch it. But when you persist it key of "some" will still be saved.
You can have a field "SomeThing some" without @Load; this object acts as what I've been casually calling a "partial entity" or what JohnP called an "unactivated entity" in Twig. It's just an entity whose key fields have been set but no others. The native datastore type is a simple Key.
Note that Ref<Thing> without a @Load annotation is not loaded from the datastore. If you call get() on an uninitialized Ref you will get an exception. (note that getValue() returns null so that json serializers don't freak out)
@Index Key<Thing> thingKey;
@Ignore Thing cacheThing;
public void getThing() {
if (cacheThing == null)
cachedThing = ofy.load().key(thingKey).safeGet();
return cacheThing;
}
You can have a field "SomeThing some" without @Load; this object acts as what I've been casually calling a "partial entity" or what JohnP called an "unactivated entity" in Twig. It's just an entity whose key fields have been set but no others. The native datastore type is a simple Key.That's what I wanted to create without querying the datastore. Is that possible? If I have the id, I can create a key and that's all the datastore needs. But I can't find a way to create a partial entity similar to Key.create(...).
Note that Ref<Thing> without a @Load annotation is not loaded from the datastore. If you call get() on an uninitialized Ref you will get an exception. (note that getValue() returns null so that json serializers don't freak out)So if I understand that correctly, I cannot call "get()" on a Ref I created via Ref.create(Key.create(...))?
Seems that I completely misunderstood the Refs... I thought they were the things that I used to create manually:@Index Key<Thing> thingKey;@Ignore Thing cacheThing;public void getThing() {
if (cacheThing == null)
cachedThing = ofy.load().key(thingKey).safeGet();
return cacheThing;
}
I have a tree structure so I cannot preload the entities without getting the whole tree... I thought I could use the Refs and then just say "get()" whenever I need the entity. Hm... Maybe it's best to stick with the keys...
That's what I wanted to create without querying the datastore. Is that possible? If I have the id, I can create a key and that's all the datastore needs. But I can't find a way to create a partial entity similar to Key.create(...).You mean other than having a constructor for your SomeThing that accepts the key fields?thing = new SomeThing(thingId);
@Index Thing parentThing;
public void setParent(Long id) {
parentThing = Objectify.createPartial(Thing.class, id);
}
However, I've been thinking of moving the key manipulation code out of ObjectifyFactory and into a static. Since keys are defined statically via annotations, they never change, so this metadata is reasonable to place in a static context. The main advantage of this is that the (new) Key/Ref.equivalent() methods could work with partial entities. It would certainly be possible to have a createPartial() method but it seems a little silly.
Correct. Unless you use the (recently checked in) Ref.create(key, entity) method.
The Ref is probably exactly what you want here, but you still need to fetch it explicitly.
@Index Ref<Thing> thing;public void getThing() {if (thing.getValue() == null)ofy.load().ref(thing);return thing.getValue();}
thing = ofy.load().ref(thing);
It would even be possible to create a SmartRef which will go to the datastore ad-hoc. You could actually do this right now just by registering a special TranslatorFactory which understands SmartRef, based on the existing RefTranslator. You have to be careful of transaction boundaries, however... an Objectify instance in a transactional context is no good after the end of the transaction.
Yes, because I don't want to create a new Thing, I just want to set a relationship. Like:
[...]
Yes. My misunderstanding. I thought I could create the partial and Objectify would magically load it from the database once I first invoke one of its methods...
I'll give it a try once 4.0a4 is out. (Just out of curiosity: Why is the key not automatically created from the entity id?)
The Ref is probably exactly what you want here, but you still need to fetch it explicitly.
@Index Ref<Thing> thing;public void getThing() {if (thing.getValue() == null)ofy.load().ref(thing);return thing.getValue();}Don't I have to say:thing = ofy.load().ref(thing);
Sounds interesting. I'll have a look at the docs.
Yes, because I don't want to create a new Thing, I just want to set a relationship. Like:[...]Yes. My misunderstanding. I thought I could create the partial and Objectify would magically load it from the database once I first invoke one of its methods...Ah, you want Thing to be a dynamic proxy. This kind of stuff is possible with the new Translator system in Ofy4, but I myself am unlikely to implement it anytime soon. You would need to include cglib or some other bytecode generator to build these, and there are probably all kinds of consequences to doing this that need to be thought through.
Look for this in the next couple weeks, it has become a pressing need of my own. We should be able to say:Key.create(thing);Ref.create(thing);
Don't I have to say:thing = ofy.load().ref(thing);Nope. ofy.load().ref(thingRef) actually sets the Result<Thing> inside that Ref instance. In this way it behaves a little differently from the key() and entity() methods. But it is much more useful, especially when you have Ref objects that get passed around various places.
Sounds interesting. I'll have a look at the docs.I hope you mean javadocs, because unfortunately this is the only place the documentation lives right now.
And I haven't yet gotten around to rewriting the big javadoc on Transmog that explains how the whole system works.
I've been working frantically on Voost (which, btw, has launched: https://www.voo.st/) and getting a lot of practical experience with Ofy4.
I've been working frantically on Voost (which, btw, has launched: https://www.voo.st/) and getting a lot of practical experience with Ofy4.Looks cool. But: The closest event to Germany is in Eyjafjallajökull, Iceland :-D.
Look for this in the next couple weeks, it has become a pressing need of my own. We should be able to say:Key.create(thing);Ref.create(thing);
public <T extends Model> Ref<T> safeCreateRef(final T entity) {Ref<T> ref;try {ref = Ref.create(entity);} catch (IllegalArgumentException e) {save().entity(entity).now();ref = Ref.create(entity);}return ref;}
I think objectify with guice should be @RequestScoped so you get a new objectify per request.
I think objectify with guice should be @RequestScoped so you get a new objectify per request.
Objectify 4 has a session cache enabled by default. The only place in the wiki I could find that text is the design doc for Ofy4 - is there somewhere else?
Note that it does not currently return the exact entity object instance - the cache only holds the raw data, which gets translated into a new object instance every load(). This is something that will change prior to a "real release" -- after a lot of hands-on experience, plus a lot of discussion with Guido, we have decided that NDB and Objectify should both attempt to unify to single object instances in the session.
What version of Ofy are you using?
Also: It would help a lot if you posted a small snippet of code or pseudocode to describe what you are doing. I'm having a hard time picturing it.