[RequestFactory] Updating a proxy

51 views
Skip to first unread message

Hilco Wijbenga

unread,
Sep 29, 2011, 3:10:46 AM9/29/11
to GWT Users
Hi all,

Let's say I have a ThingProxy which has a GizmoProxy (and other stuff):

interface ThingProxy {
GizmoProxy getGizmo();
void setGizmo(GizmoProxy gizmo);
... other getters and setters ...
}

I need an instance of ThingProxy everywhere so I keep one available.
At some point I need to update something in its GizmoProxy. And that's
when the fun starts...

I see two approaches and neither works. :-(

First approach:
ThingProxy thing = ...;
ThingRequest thingRequest = RequestBuilder.newThingRequest();
ThingProxy mutableThing = thingRequest.edit(thing);
GizmoProxy mutableGizmo = mutableThing.getGizmo();
mutableGizmo.setSomething(...);
thingRequest.persist(mutableThing).fire(... receiver ...);

Unfortunately, this doesn't save the changes in the GizmoProxy.
Apparently, RF sees that the ThingProxy hasn't changed so it does
nothing (at least, nothing with the GizmoProxy).

Second approach:
ThingProxy thing = ...;
GizmoRequest gizmoRequest = RequestBuilder.newGizmoRequest();
GizmoProxy mutableGizmo = gizmoRequest.edit(thing.getGizmo());
mutableGizmo.setSomething(...);
gizmoRequest.persist(mutableGizmo).fire(... receiver ...);

This works fine and saves the change in GizmoProxy. But now ThingProxy
is out of date and I can't call setGizmo because the ThingProxy is
frozen. I tried using a mutable ThingProxy but then I get errors later
on (object is being edited in a different context).

How am I supposed to do this?

Cheers,
Hilco

Thomas Broyer

unread,
Sep 29, 2011, 4:33:11 AM9/29/11
to google-we...@googlegroups.com
The first approach should be the good one. Have you checked what goes on the wire? Are there really no operations being sent (look for an "O" property in the JSON)?

(FYI: in RF, there are operations on proxies, and invocations on services)

Hilco Wijbenga

unread,
Sep 29, 2011, 3:27:21 PM9/29/11
to google-we...@googlegroups.com

Sure, lots of them (user = ThingProxy, basicUser = GizmoProxy [the
level should change from 0 to 1 in basicUser]):

{
"F":"com.company.domain.request.api.DomainRequest$Builder",
"O":
[
{
"T":"MUhstLEy3myftqjcUSbJiXK7UTM=",
"P":
{
"balanceDelta":1000,
"currentBalance":1000,
"currencyType":
{
"T":"2SkAFKlbopWmIXRi$HD1bTQ3ppo=",
"S":"Mi4w"
},
"comment":"Level Up to Level 1.",
"user":
{
"T":"XsxUVV_IoQWMCnw5Bk9oWk4mU$g=",
"S":"NS4w"
}
},
"C":1,
"R":"1",
"O":"PERSIST"
},
{
"T":"XsxUVV_IoQWMCnw5Bk9oWk4mU$g=",
"V":"MS4w",
"P":
{
"basicUser":
{
"T":"KrJKdAdDPLkbzJh$UCC4PAyD9tQ=",
"S":"NS4w"
},
"friendsGroup":
{
"T":"dPtqVUr1qWygjnZOjOHwDI3E_6c=",
"S":"Ni4w"
}
},
"S":"NS4w",
"O":"UPDATE"
},
{
"T":"KrJKdAdDPLkbzJh$UCC4PAyD9tQ=",
"V":"MS4w",
"P":
{
"account":
{
"T":"Inde8rw1dKVFojfvX8WlUbtX84M=",
"S":"NS4w"
}
},
"S":"NS4w",
"O":"UPDATE"
},
{
"T":"Inde8rw1dKVFojfvX8WlUbtX84M=",
"V":"MS4w",
"S":"NS4w",
"O":"UPDATE"
},
{
"T":"dPtqVUr1qWygjnZOjOHwDI3E_6c=",
"V":"MS4w",
"P":
{
"groupType":
{
"T":"PZiZQZ4KxLWrDDpldtcde_S3dQE=",
"S":"MS4w"
}
},
"S":"Ni4w",
"O":"UPDATE"
},
{
"T":"PZiZQZ4KxLWrDDpldtcde_S3dQE=",
"V":"MS4w",
"S":"MS4w",
"O":"UPDATE"
}
],
"I":
[
{
"P":
[
{
"T":"MUhstLEy3myftqjcUSbJiXK7UTM=",
"R":"1",
"C":1
}
],
"O":"xWeECnR2JMgNVkoxRJcKT3orPAE="
}
]
}

Hilco Wijbenga

unread,
Sep 29, 2011, 3:35:45 PM9/29/11
to google-we...@googlegroups.com
On 29 September 2011 12:27, Hilco Wijbenga <hilco.w...@gmail.com> wrote:
> On 29 September 2011 01:33, Thomas Broyer <t.br...@gmail.com> wrote:
>> The first approach should be the good one. Have you checked what goes on the
>> wire? Are there really no operations being sent (look for an "O" property in
>> the JSON)?
>> (FYI: in RF, there are operations on proxies, and invocations on services)
>
> Sure, lots of them (user = ThingProxy, basicUser = GizmoProxy [the
> level should change from 0 to 1 in basicUser]):

For the record, my UserService *is* being called, just not the
BasicUserService. It really should be the other way around.

Thomas Broyer

unread,
Sep 30, 2011, 3:54:05 AM9/30/11
to google-we...@googlegroups.com
Why would BasicUserService ( = GizmoService?) be called if you invoke UserRequest#persist(UserProxy) ( = ThingRequest#persist(ThingProxy) ?)

It's hard to follow what you're expecting given that you switched from thing/gizmo/setSomething to [something-else]/user/basicUser/set[what?]

Could you post the actual code that led to the request whose payload you posted already?

Hilco Wijbenga

unread,
Sep 30, 2011, 3:26:22 PM9/30/11
to google-we...@googlegroups.com
On 30 September 2011 00:54, Thomas Broyer <t.br...@gmail.com> wrote:
> Why would BasicUserService ( = GizmoService?) be called if you invoke
> UserRequest#persist(UserProxy) ( = ThingRequest#persist(ThingProxy) ?)

Because BasicUserProxy is part of UserProxy.

> It's hard to follow what you're expecting given that you switched from
> thing/gizmo/setSomething to [something-else]/user/basicUser/set[what?]

I wanted to keep it generic but see below.

> Could you post the actual code that led to the request whose payload you
> posted already?

Here you go:

final UserRequest userRequest =
domainRequestApi.getRequestBuilder().newUserRequest();
final UserProxy mutableUser =
userRequest.edit(domainRequestApi.getCache().getCurrentUser());
final BasicUserProxy mutableBasicUser = mutableUser.getBasicUser();
final int level = mutableBasicUser.getLevel();
final int basicCurrencyDelta = ...;
final int premiumCurrencyDelta = ...;
final int newLevel = level + 1;
final String comment = "Level Up to Level " + newLevel + ".";
mutableBasicUser.setLevel(newLevel);
final Receiver<Integer> levelUpReceiver = new LevelUpReceiver(...,
mutableUser, comment, basicCurrencyDelta, premiumCurrencyDelta);
userRequest.persist(mutableUser).fire(levelUpReceiver);

LevelUpReceiver's onSuccess simply fires some events, including one
that sends out the by then presumably updated UserProxy (hence the
mutableUser in the constructor).

Given that BasicUserProxy is part of UserProxy, I would expect its
changes to be stored, i.e. I expect the entire object graph that is
UserProxy to be stored (as necessary).

Just to be complete:

@ProxyForName(value = "com.company.domain.api.User", locator =
"com.company.domain.service.api.UserService")
public interface UserProxy extends EntityProxy
{
BasicUserProxy getBasicUser();
void setBasicUser(BasicUserProxy basicUser);
... a few others ...
}

@ProxyForName(value = "com.company.domain.api.BasicUser", locator =
"com.company.domain.service.api.BasicUserService")
public interface BasicUserProxy extends EntityProxy
{
int getLevel();
void setLevel(int level);
... a few others ...
}

In very short pseudo code, what I want to happen is the following:

User.BasicUser.Level := User.BasicUser.Level + 1;
Persist User (or BasicUser, I don't care);
Make sure that User.BasicUser.Level == BasicUser.Level, i.e. User's
BasicUser should be the newly updated one.

(I've tried persisting User but then BasicUser's changes are ignored;
persisting BasicUser works but then User references an outdated
BasicUser; I tried using the Editor framework but that didn't make any
difference either.)

Thomas Broyer

unread,
Sep 30, 2011, 6:08:41 PM9/30/11
to google-we...@googlegroups.com


On Friday, September 30, 2011 9:26:22 PM UTC+2, Hilco Wijbenga wrote:
On 30 September 2011 00:54, Thomas Broyer <t.br...@gmail.com> wrote:
> Why would BasicUserService ( = GizmoService?) be called if you invoke
> UserRequest#persist(UserProxy) ( = ThingRequest#persist(ThingProxy) ?)

Because BasicUserProxy is part of UserProxy.

If you call a method on UserRequest, the equivalent method will be called on its @Service (i.e. UserService), and no other service method will be called.
If you call setters on proxies (UserProxy, BasicUserProxy), the equivalent setters are called on their @ProxyFor (User, BasicUser).

The only "special namings" RequestFactory uses are the static findXxx(), and the getId() and getVersion() methods; and only if you do not use a Locator. A method called "persist" is in no way a special method.

What I mean is that calling UserRequest#persist(UserProxy) cannot imply a call to, say, BasicUserService#persist(BasicUser) on the server; only UserService#persist(User) will be called.
 

> It's hard to follow what you're expecting given that you switched from
> thing/gizmo/setSomething to [something-else]/user/basicUser/set[what?]

I wanted to keep it generic but see below.

> Could you post the actual code that led to the request whose payload you
> posted already?

Here you go:

final UserRequest userRequest =
domainRequestApi.getRequestBuilder().newUserRequest();
final UserProxy mutableUser =
userRequest.edit(domainRequestApi.getCache().getCurrentUser());
final BasicUserProxy mutableBasicUser = mutableUser.getBasicUser();
final int level = mutableBasicUser.getLevel();
final int basicCurrencyDelta = ...;
final int premiumCurrencyDelta = ...;
final int newLevel = level + 1;
final String comment = "Level Up to Level " + newLevel + ".";
mutableBasicUser.setLevel(newLevel);
final Receiver<Integer> levelUpReceiver = new LevelUpReceiver(...,
mutableUser, comment, basicCurrencyDelta, premiumCurrencyDelta);
userRequest.persist(mutableUser).fire(levelUpReceiver);


What's strange is that there's a "comment": "Level Up to Level 1." in the JSON payload of the request, while I don't see a setComment() above; and there's no "level":1 while there is a setLevel(newLevel).

 

Given that BasicUserProxy is part of UserProxy, I would expect its

changes to be stored, i.e. I expect the entire object graph that is
UserProxy to be stored (as necessary).


What do you mean exactly by "stored"? What RequestFactory will do on the server side is:
  1. get User and BasicUser (and others, as needed) by their ID
  2. "apply the diff" from the request to these objects, by calling the appropriate setters.
  3. call the service methods; here, call UserService#persist(User) with the User instance modified above
So actually persisting the "entire object graph" is up to the UserService#persist(User) method; as the doc says:
“RequestFactory automatically sends the whole object graph in a single request. In this case, the implementation of Person.persist() on the server is responsible for persisting the related Address also, which may or may not happen automatically, depending on the ORM framework and how the relationship is defined.” (the part about @Embedded objects not being supported is no longer true since GWT 2.1.1 and the introduction of ValueProxy; but that doesn't apply here)

So, is your problem that BasicUser is not persisted? or that the User#getBasicUser() passed to the persist() method doesn't have its level modified?

Or is your issue that domainRequestApi.getCache().getCurrentUser() doesn't reflect the change? In that case, then unless you, somewhere, setCurrentUser with the "mutableUser", that's normal: immutable proxies are never updated, they're immutable snapshots.

Hilco Wijbenga

unread,
Sep 30, 2011, 6:59:31 PM9/30/11
to google-we...@googlegroups.com

:-) I apologise for "comment", that's being done by a separate event.
The JSON you see contains two proxies going to the server. I didn't
want to change the JSON as I was not clear on the exact dependencies.

I've wondered about the missing "level":1 as well. I know I've seen it
once but that may have been when I was persisting BasicUser directly.

>> Given that BasicUserProxy is part of UserProxy, I would expect its
>>
>> changes to be stored, i.e. I expect the entire object graph that is
>> UserProxy to be stored (as necessary).
>
> What do you mean exactly by "stored"? What RequestFactory will do on the
> server side is:

I mean persisting it in the database.

> get User and BasicUser (and others, as needed) by their ID
> "apply the diff" from the request to these objects, by calling the
> appropriate setters.
> call the service methods; here, call UserService#persist(User) with the User
> instance modified above

Yeah, I had assumed it would call BasicUserService#persist(BasicUser) as well.

> So actually persisting the "entire object graph" is up to the
> UserService#persist(User) method; as the doc says:
> “RequestFactory automatically sends the whole object graph in a single
> request. In this case, the implementation of Person.persist() on the server
> is responsible for persisting the related Address also, which may or may not
> happen automatically, depending on the ORM framework and how the
> relationship is defined.” (the part about @Embedded objects not being
> supported is no longer true since GWT 2.1.1 and the introduction of
> ValueProxy; but that doesn't apply here)
> — http://code.google.com/webtoolkit/doc/latest/DevGuideRequestFactory.html#relationships

All right, I guess I was expecting too much.

> So, is your problem that BasicUser is not persisted? or that the
> User#getBasicUser() passed to the persist() method doesn't have its level
> modified?
> Or is your issue that domainRequestApi.getCache().getCurrentUser() doesn't
> reflect the change? In that case, then unless you, somewhere, setCurrentUser
> with the "mutableUser", that's normal: immutable proxies are never updated,
> they're immutable snapshots.

I have User cached and User contains BasicUser. I want BasicUser
updated on the server and I want that change reflected in User (so
User.getBasicUser().getLevel() should yield the new level). This seems
impossible without rerequesting the entire User object graph from the
server. That's very expensive and totally unnecessary (in the sense
that all information is already available on the client). But I don't
see how I can "update" the cached User with the "new" BasicUser. I
can't call setBasicUser on User because it's frozen.

So given what you've told me above, my question becomes: how do I
update User after persisting BasicUser? Without rerequesting the whole
User object graph from the server, of course. This must be easy but I
don't see how to do it. :-(

Thomas Broyer

unread,
Sep 30, 2011, 7:08:28 PM9/30/11
to google-we...@googlegroups.com
If you edit() and persist User (what you did in the code sample you sent), then the "mutableUser" will be frozen at the time you fire() the request, and if all goes well (i.e. in the Receiver's onSuccess), you could then just use the "mutableUser" instance as your new "cached" user.

Hilco Wijbenga

unread,
Sep 30, 2011, 7:43:33 PM9/30/11
to google-we...@googlegroups.com

:-) Yes, but then BasicUser doesn't get persisted which was the whole
point of the operation.

And server side [i.e. in UserService#persist(User)] I have no idea
what changed in User (only RF would know that). I really don't want to
have save the entire User graph just to update one integer.

What I've got working now is persisting BasicUser and requesting User
from the server afterwards. Very expensive and wasteful but at least
that works. Surely there must be a better way?

Thomas Broyer

unread,
Sep 30, 2011, 8:51:33 PM9/30/11
to google-we...@googlegroups.com
Never tried it but how about edit()ing User but only persist()ing its BasicUser?

(i.e. do as if you'd save the "whole object graph", but call persist on your BasicUserProxy instead of UserProxy; you'd still have an immutable UserProxy, with the changes to you made to its BasicUserProxy)
Reply all
Reply to author
Forward
0 new messages