[TransWarp] Functionality Gaps
Phillip J. Eby
pje at telecommunity.com
Tue Jul 8 17:56:55 EDT 2003
These are some notes on functionality gaps I encountered in PEAK while
working on the 'bulletins' example app. This is not necessarily a complete
list; I expect to add more to it eventually. :(
Awkward Access to Services
--------------------------
Complex applications involve a lot of services, especially data managers
(DMs). DMs that do O-R mapping tend to refer to each other, so they need
to be able to find one another. Using a spatial mapping (e.g.
bindTo('../anotherDM')) leads to a rigid component structure and
duplication. Using a configuration lookup means needing to either define
an arbitrary property or interface to use as a key, and then it still needs
to be repeated in several places. Because this is an artificial key, it's
still awkward to use. I think the solution to this would be to create a
new IConfigKey implementation that took a class or classes as
parameters. The idea being that you might say things like:
SomeDM = binding.New('some.dmclass', offerAs=[storage.DMFor(SomeClass)])
OtherDM = binding.New('other.dmclass',
offerAs=[storage.DMFor(OtherClass)])
in an application class to instantiate and offer the components. Then, in
SomeDM, you might say:
myOtherDM = binding.bindTo(storage.DMFor(OtherClass))
to create inter-dm links. Because this is based on the classes themselves,
the linkages are tied directly to the application's semantics, rather than
being an artificial key based on a coding convention. I'm not fond of
'DMFor()' as a name for this, and I also don't know what to call the
concept in general.
The second way in which service access is awkward, is accessing a DM
service from an Element. If you want an Element to create another Element,
it needs to call a DM's newItem() method. Currently, since Elements aren't
components and don't really support bindings, you'd have to do something like:
def someElementMethod(self, blah...):
dm = binding.lookupComponent(self, storage.DMFor(OtherClass))
other = dm.newItem(OtherClass)
Ugh. This isn't really fixed by the 'DMFor()', either. The issue here is
that the created OtherClass instance won't be saved unless it's associated
with a DM that can take care of it. For one-to-one relationships (and
one-to-many if there is a "pointer" for each item on the "many" side), this
can actually be handled automatically at object _save() time (using
'OtherDM.oidFor()'), but for any other relationship, the object *must* have
its DM established at creation time.
Probably, the simplest way to do this, once we have a 'storage.DMFor()'
standard, would be to add a new API like:
def someElementMethod(self, blah...):
other = storage.newElement(self, OtherClass)
Then, the 'newElement()' method would do the 'binding.lookupComponent(self,
storage.DMFor(OtherClass))' part. This at least is now a straightforward
idiom.
To implement 'DMFor()', we'll need to take into consideration the fact that
some DM's are polymorphic. For example, DMs that use pickling techniques
(e.g. XMI, ZODB, etc.) will likely be used for any of several objects. It
seems that we need configuration lookup for these items to work such that
looking up 'storage.DMFor(X)' will be able to return 'storage.DMFor(Y)' if
X is a subclass of Y and nothing is registered for
'storage.DMFor(X)'. This will require some refactoring of the
'config.PropertyMap' class, and probably EigenRegistry as well. :(
Data Managers
-------------
Being able to create storage.EntityDM's sure beats the heck out of not
having EntityDM's. But they also leave a lot to be desired in certain areas.
First, there really isn't a good way to handle OID's that are represented
*in* the model. For example, in the Bulletins domain model, users have a
login ID that is also used to retrieve instances. This pattern actually
occurs in different ways for all of the Bulletins types, and is probably
the common case for most business application objects (usually only
log-like event records are lacking in model-defined primary keys). Maybe
there needs to be an EntityDM subclass that fills in more of these standard
patterns, that you use for non-pickling DMs.
Second, we have negligible cross-DB SQL compatibility. Because the DBAPI
allows multiple parameter styles, for example, but also because type
conversion is difficult. Also, knowledge of tables, keys, conversions,
etc. is scattered across a given codebase, such that the code will tend to
be sensitive even to minor data model changes. This isn't good. For any
application more complex than the Bulletins example, we're going to need
some kind of abstraction. The simplest thing that comes to mind is to have
Table (really, "tabular data") objects that have some select/insert/update
methods. We're also going to need some better abstractions for DB-specific
API variations (e.g. the DBAPI paramstyle), and these are probably going to
have to be added to the SQLConnection type. This is a fairly sizeable
chunk of work, and it'll may have to wait till the 0.6 release cycle. :(
Third, a standard idiom doesn't exist currently for a DM to handle
non-existent objects, or to handle the addition of an object with the same
OID as an existing object. Currently, retrieving a non-existent OID hands
you a "broken" object that may or may not work properly. This is sort of
like an "infinite Rack" in ZPatterns terms. I don't know what to do about
this. Adding an existence check could result in quite a bit of processing
overhead. It seems like we might need two ways to get an item from a DM: a
way used by other DMs to get "ghosts" (where a broken reference really
should be considered broken), and a way used by application code when
looking up keys that come from an external source.
More information about the PEAK
mailing list