[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
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