[TransWarp] Bidirectional association handling

Phillip J. Eby pje at telecommunity.com
Sun Nov 17 17:55:44 EST 2002


* Ensure that both ends of an association are consistent; if one end is 
changed, the other should reflect the change.  Modifying one end, however, 
should ideally *not* cause the other end's state to be loaded, unless the 
other end is the one that actually stores the pointer!

* Allow the structural feature at each end of the association to check any 
changes to ensure they are valid according to domain-model constraints

* Handle situations where the underlying implementation stores a pointer 
(e.g. a foreign key reference) in only one direction, or where pointers 
exist in both directions.

* Handle situations where the objects are not being persisted by way of a 
DM, e.g. simple in-memory usage of Element classes.

* Provide errors or warnings in the event that an association is configured 
or implemented improperly, in the sense that it's not possible for the 
association to work.  That is, if neither side is actually storing a 
pointer, or the non-storing side has no way to reference the storing side.

* Work cleanly with PersistentQuery objects returned by QueryDMs.

The current implementation ensures consistency and works with or without a 
DM, and with pointers on one or both sides, but it may cause unnecessary 
data loads.  It does not provide error checking, and won't work with 
PersistentQuery objects yet.

It might sound like those aren't big shortcomings, but actually they can be 
pretty nasty, especially the unnecessary data loads.  Let's say that you 
have a "folder" that contains thousands of entries, and you unlink an item 
from it.  If this is implemented such that the entry contains a foreign key 
reference to the folder, and the folder implements its end of the 
association as a query, then this could unnecessarily run the query!  Yet, 
if the query was loaded, we wouldn't want it to be out-of-date, so we'd 
need to modify it in-place.

It gets worse...  if the transaction is rolled back, and the query object 
is cached, it needs to have its state reset, or it'll carry corrupted data 
into the next transaction.  Not good.

It does sound, however, as though the "virtual" sides of the associations 
are the bottleneck.  That is, if neither side of the association is 
calculated or queried, then everything works cleanly.  Which means if we 
could get such a "virtual" list to behave sufficiently like a real one, 
we'd be all set.

If a virtual list didn't load its state when a modification was attempted, 
and could ensure that its state, if modified, was reset upon transaction 
abort, that would probably do the trick.  The only thing left to figure out 
would be how to check that you haven't set up both association ends with 
virtual lists!

Unfortunately, at this moment, I have no idea how to do any of those things 
except the part where the virtual list doesn't load its state 
unnecessarily.  I'm assuming the easy way to do that is to have a 
VirtualList proxy class that wraps over a PersistentQuery.

Ooh, actually, if the VirtualList class isn't Persistent, then the Element 
holding it will consider itself modified when it modifies the virtual 
list...  which means the Element's DM will be responsible for aborting 
it.  Since the VirtualList is a one-off for that Element, it won't hold a 
reference to the query any longer, so nothing is carried forward.  The 
VirtualList could keep its own copy of the underlying list once it's 
modified; the only problem is on commit.  What happens when you 
commit?  The Element's DM might not reset the Element, so the list might 
hang around.  I guess it'll have to always verify that the underlying query 
has its state loaded; if an access is attempted when the query isn't 
loaded, it should ditch its modifications and reload the underlying query.

Yeah, I think that'll work.  No time to implement it right now, but it 
sounds like it should work.  In the load() method of the DM that manages 
the Element with the virtual list, you'll say something like:

state['linkAttr'] = model.QueryLink(foreignQueryDM[myPrimaryKey])

Before you return the state.  (QueryLink seems a more intent-driven name 
than VirtualList.)  Okay, I think that's got it.

(Hardly seems like there's any point to sending this e-mail now, but what 
the heck?  It's a record of the requirements and the design process, and if 
this turns out not to be a solution I'll be able to come back to these 
notes.  Also, I haven't addressed the error checking part yet.)

More information about the PEAK mailing list