Automatically aligning ObjectContexts when establishing a relationship

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Automatically aligning ObjectContexts when establishing a relationship

Andrus Adamchik
We've had a concept of "persistence by reachability" for a long time. When a relationship is established between 2 objects, if one of them is in an ObjectContext and another one is not, we'd automatically register the transient object using the context of the persistent objects.

Now consider a different case... If a relationship is established between 2 objects that are both registered, but in 2 different contexts, the current behavior is to throw an exception. I am trying to decide whether it is wise to resolve cross-context conflict quietly instead, replacing the target object with a copy local to the source ObjectContext by calling ObjectContext.localObject(..). This should remove a fair amount of boilerplate if you are using multiple contexts throughout the app as I do.

It doesn't come free though. The the code then becomes more obscure and error-prone. E.g.:

A a = ; // in c1
B b = ; // in c2

a.setB(b); // equivalent to a.setB(c1.localObject(b))
                        // i.e. we are attaching a different copy of B now.

b.setProp("newValue"); // we may not realize that we are changing the wrong copy...
c1.commitChanges(); // "b" changes are not committed. oops...

I am leaning towards leaving things as they are, but maybe someone has better ideas on how to keep it both convenient and robust at the same time :)

Thanks,
Andrus
Reply | Threaded
Open this post in threaded view
|

Re: Automatically aligning ObjectContexts when establishing a relationship

Aristedes Maniatis-2
On 19/12/16 10:00pm, Andrus Adamchik wrote:
> a.setB(b); // equivalent to a.setB(c1.localObject(b))
> // i.e. we are attaching a different copy of B now.
>
> b.setProp("newValue"); // we may not realize that we are changing the wrong copy...
> c1.commitChanges(); // "b" changes are not committed. oops...

And this gets even messier if the related object is new and not yet persisted at all. And if there are constraints.

I'm trying to imagine what sort of app hits these types of issues. Is this typically where there is one big read-only context and then a user might modify some of those records, so they are copied into a local read-write context for that user?


Ari



--
-------------------------->
Aristedes Maniatis
GPG fingerprint CBFB 84B4 738D 4E87 5E5C  5EFA EF6A 7D2E 3E49 102A
Reply | Threaded
Open this post in threaded view
|

Re: Automatically aligning ObjectContexts when establishing a relationship

Andrus Adamchik
Yes, exactly. When your "editing" contexts are narrowly scoped, you have to transfer objects all the time.

And now I am also dealing with an issue of framework support (Tapestry specifically). A master object on an editor page lives in a page-scoped context. Master's to-one relationships are modified via a dropdown (<t:select value="mainObject.rel"/>). So setters are called by Tapestry, and related objects are deserialized with Tapestry ValueEncoder, ending up in a shared context. I have a working solution outside Cayenne (tracking my page-scoped context via a service, and making it accessible to the ValueEncoder). But hoped I could do it directly somehow.

Andrus


> On Dec 19, 2016, at 2:55 PM, Aristedes Maniatis <[hidden email]> wrote:
>
> On 19/12/16 10:00pm, Andrus Adamchik wrote:
>> a.setB(b); // equivalent to a.setB(c1.localObject(b))
>> // i.e. we are attaching a different copy of B now.
>>
>> b.setProp("newValue"); // we may not realize that we are changing the wrong copy...
>> c1.commitChanges(); // "b" changes are not committed. oops...
>
> And this gets even messier if the related object is new and not yet persisted at all. And if there are constraints.
>
> I'm trying to imagine what sort of app hits these types of issues. Is this typically where there is one big read-only context and then a user might modify some of those records, so they are copied into a local read-write context for that user?
>
>
> Ari
>
>
>
> --
> -------------------------->
> Aristedes Maniatis
> GPG fingerprint CBFB 84B4 738D 4E87 5E5C  5EFA EF6A 7D2E 3E49 102A

Reply | Threaded
Open this post in threaded view
|

Re: Automatically aligning ObjectContexts when establishing a relationship

Michael Gentry-2
In reply to this post by Andrus Adamchik
I think the current behavior should be kept, or at least the default.  I
can see some value in being able to do this, though:

c1.setAutomaticContextBridging(true);

Allow the developer to set it on a per-context basis when it simplifies
their code and is more explicit and localized.

mrg


On Mon, Dec 19, 2016 at 6:00 AM, Andrus Adamchik <[hidden email]>
wrote:

> We've had a concept of "persistence by reachability" for a long time. When
> a relationship is established between 2 objects, if one of them is in an
> ObjectContext and another one is not, we'd automatically register the
> transient object using the context of the persistent objects.
>
> Now consider a different case... If a relationship is established between
> 2 objects that are both registered, but in 2 different contexts, the
> current behavior is to throw an exception. I am trying to decide whether it
> is wise to resolve cross-context conflict quietly instead, replacing the
> target object with a copy local to the source ObjectContext by calling
> ObjectContext.localObject(..). This should remove a fair amount of
> boilerplate if you are using multiple contexts throughout the app as I do.
>
> It doesn't come free though. The the code then becomes more obscure and
> error-prone. E.g.:
>
> A a = ; // in c1
> B b = ; // in c2
>
> a.setB(b);              // equivalent to a.setB(c1.localObject(b))
>                         // i.e. we are attaching a different copy of B now.
>
> b.setProp("newValue");  // we may not realize that we are changing the
> wrong copy...
> c1.commitChanges();     // "b" changes are not committed. oops...
>
> I am leaning towards leaving things as they are, but maybe someone has
> better ideas on how to keep it both convenient and robust at the same time
> :)
>
> Thanks,
> Andrus
Reply | Threaded
Open this post in threaded view
|

Re: Automatically aligning ObjectContexts when establishing a relationship

John Huss
In reply to this post by Andrus Adamchik
I agree that the current behavior is better.  However, adding a
localObjects(...) method that takes a var-args list would be helpful.
deleteObjects is already like this.

On Mon, Dec 19, 2016 at 5:01 AM Andrus Adamchik <[hidden email]>
wrote:

> We've had a concept of "persistence by reachability" for a long time. When
> a relationship is established between 2 objects, if one of them is in an
> ObjectContext and another one is not, we'd automatically register the
> transient object using the context of the persistent objects.
>
> Now consider a different case... If a relationship is established between
> 2 objects that are both registered, but in 2 different contexts, the
> current behavior is to throw an exception. I am trying to decide whether it
> is wise to resolve cross-context conflict quietly instead, replacing the
> target object with a copy local to the source ObjectContext by calling
> ObjectContext.localObject(..). This should remove a fair amount of
> boilerplate if you are using multiple contexts throughout the app as I do.
>
> It doesn't come free though. The the code then becomes more obscure and
> error-prone. E.g.:
>
> A a = ; // in c1
> B b = ; // in c2
>
> a.setB(b);              // equivalent to a.setB(c1.localObject(b))
>                         // i.e. we are attaching a different copy of B now.
>
> b.setProp("newValue");  // we may not realize that we are changing the
> wrong copy...
> c1.commitChanges();     // "b" changes are not committed. oops...
>
> I am leaning towards leaving things as they are, but maybe someone has
> better ideas on how to keep it both convenient and robust at the same time
> :)
>
> Thanks,
> Andrus
Reply | Threaded
Open this post in threaded view
|

Re: Automatically aligning ObjectContexts when establishing a relationship

Andrus Adamchik
Good idea.. Also a flavor that takes a collection arg.

Andrus

> On Dec 20, 2016, at 6:06 AM, John Huss <[hidden email]> wrote:
>
> I agree that the current behavior is better.  However, adding a
> localObjects(...) method that takes a var-args list would be helpful.
> deleteObjects is already like this.
>
> On Mon, Dec 19, 2016 at 5:01 AM Andrus Adamchik <[hidden email]>
> wrote:
>
>> We've had a concept of "persistence by reachability" for a long time. When
>> a relationship is established between 2 objects, if one of them is in an
>> ObjectContext and another one is not, we'd automatically register the
>> transient object using the context of the persistent objects.
>>
>> Now consider a different case... If a relationship is established between
>> 2 objects that are both registered, but in 2 different contexts, the
>> current behavior is to throw an exception. I am trying to decide whether it
>> is wise to resolve cross-context conflict quietly instead, replacing the
>> target object with a copy local to the source ObjectContext by calling
>> ObjectContext.localObject(..). This should remove a fair amount of
>> boilerplate if you are using multiple contexts throughout the app as I do.
>>
>> It doesn't come free though. The the code then becomes more obscure and
>> error-prone. E.g.:
>>
>> A a = ; // in c1
>> B b = ; // in c2
>>
>> a.setB(b);              // equivalent to a.setB(c1.localObject(b))
>>                        // i.e. we are attaching a different copy of B now.
>>
>> b.setProp("newValue");  // we may not realize that we are changing the
>> wrong copy...
>> c1.commitChanges();     // "b" changes are not committed. oops...
>>
>> I am leaning towards leaving things as they are, but maybe someone has
>> better ideas on how to keep it both convenient and robust at the same time
>> :)
>>
>> Thanks,
>> Andrus

Reply | Threaded
Open this post in threaded view
|

Re: Automatically aligning ObjectContexts when establishing a relationship

Andrus Adamchik
In reply to this post by Andrus Adamchik
> (sending this back to user@)

Sorry, was supposed to be dev@ :)


> On Dec 20, 2016, at 9:07 AM, Andrus Adamchik <[hidden email]> wrote:
>
> (sending this back to user@)
>
> Actually we detect context mismatch and throw inside the setter:
>
> https://github.com/apache/cayenne/blob/master/cayenne-server/src/main/java/org/apache/cayenne/CayenneDataObject.java#L389
>
> So my idea was to use 'localObject' instead. And I agree with the rest of your assessment.
>
> Andrus
>
>
>> On Dec 20, 2016, at 2:55 AM, Aristedes Maniatis <[hidden email]> wrote:
>>
>> If a context had some sort of explicit read-only flag, then this new behaviour starts to make sense. That is:
>>
>> - objectA in read-only context
>> - edit objectB in new read-write context and join to objectA
>>
>> From what I remember, Cayenne doesn't throw an exception when you create the join, only when you try to commit. What if we throw an exception at time of join, which you could then catch and fix by copying objectA? Or if you are in setAutomaticContextBridging(true) mode, then Cayenne catches the exception and fixes it itself.
>>
>> Is that the idea you had in mind?
>>
>>
>> I think though, if we don't have an explicit 'read-only' mode for contexts, then the level of foot shooting this would enable might be quite spectacular.
>>
>>
>> Ari
>>
>>
>> On 19/12/16 11:06pm, Andrus Adamchik wrote:
>>> Yes, exactly. When your "editing" contexts are narrowly scoped, you have to transfer objects all the time.
>>>
>>> And now I am also dealing with an issue of framework support (Tapestry specifically). A master object on an editor page lives in a page-scoped context. Master's to-one relationships are modified via a dropdown (<t:select value="mainObject.rel"/>). So setters are called by Tapestry, and related objects are deserialized with Tapestry ValueEncoder, ending up in a shared context. I have a working solution outside Cayenne (tracking my page-scoped context via a service, and making it accessible to the ValueEncoder). But hoped I could do it directly somehow.
>>>
>>> Andrus
>>>
>>>
>>>> On Dec 19, 2016, at 2:55 PM, Aristedes Maniatis <[hidden email]> wrote:
>>>>
>>>> On 19/12/16 10:00pm, Andrus Adamchik wrote:
>>>>> a.setB(b); // equivalent to a.setB(c1.localObject(b))
>>>>> // i.e. we are attaching a different copy of B now.
>>>>>
>>>>> b.setProp("newValue"); // we may not realize that we are changing the wrong copy...
>>>>> c1.commitChanges(); // "b" changes are not committed. oops...
>>>>
>>>> And this gets even messier if the related object is new and not yet persisted at all. And if there are constraints.
>>>>
>>>> I'm trying to imagine what sort of app hits these types of issues. Is this typically where there is one big read-only context and then a user might modify some of those records, so they are copied into a local read-write context for that user?
>>>>
>>>>
>>>> Ari
>>>>
>>>>
>>>>
>>>> --
>>>> -------------------------->
>>>> Aristedes Maniatis
>>>> GPG fingerprint CBFB 84B4 738D 4E87 5E5C  5EFA EF6A 7D2E 3E49 102A
>>>
>>
>>
>> --
>> -------------------------->
>> Aristedes Maniatis
>> GPG fingerprint CBFB 84B4 738D 4E87 5E5C  5EFA EF6A 7D2E 3E49 102A
>