[TransWarp] Explicit is better than implicit

Phillip J. Eby pje at telecommunity.com
Sat Apr 26 18:22:36 EDT 2003


At 09:53 PM 4/24/03 -0400, Phillip J. Eby wrote:
>In some ways, these changes are simple.  In effect, PEAK as a whole will 
>be simpler when they are done, because they will allow us to trim certain 
>extraneous concepts.  For example, the notions of AppConfig and 
>SystemConfig will both go away altogether, leaving behind only the concept 
>of an "explicit root" and the "default root".  All components will belong 
>to a root, possibly the default one.  All non-component objects (e.g. 
>numbers, modules, and other non-PEAK objects) will belong to the default root.
>
>The default root component will be configured as PEAK's SystemConfig is 
>now, from 'peak.ini'.

There's a minor revision needed to the above.  After further discussion 
with Ty, I realized we need to distinguish between the "default root" and 
the "system root".  (This distinction is only important in the context of a 
PEAK application server; under typical circumstances they will be the same 
object.)

By default, the "default root" will be the same object as the "system 
root", but an application server will be able to replace the default root 
with a special error-generating object, so that applications which rely on 
using the default root will fail under circumstances where the default root 
would be shared between applications.  The "system root" will be the object 
that all non-component objects will be considered children of, and the API 
to get hold of the "system root" will be made obscure, since it nothing 
should be accessing it directly.

The system root will be configured the way the current SystemConfig is 
configured now, from 'peak.ini'.



>   APIs that now implicitly use the default root when a component isn't 
> specified will in future require it to be passed in some explicit form, 
> even if it's only in the form of explicitly passing 'None' or a constant 
> supplied by 'peak.api'.  This will ensure that you always know where your 
> properties or utilities are coming from, giving you an opportunity to 
> think about whether that's the most sensible place for them to come from.
>
>All component hierarchies will be required to terminate with an "explicit 
>root" object; that is, an object that has been explicitly designated as a 
>root object, as opposed to an object whose parent simply has not been 
>specified.  (We don't know yet how this will happen; probably an explicit 
>'ROOT' constant passed when you create the instance, or via 
>'ob.setParentComponent(ROOT)'.)

Some thoughts on how to spell this...

It should be very visible that you're making something a root object, so 
that someone reading it knows you *intend* to make it a root object.  This 
means that simply using 'None' is probably not the best idea.

OTOH, lots of code now expects getParentComponent() to return None for a 
root object.  For example, getRootComponent() looks for a root component 
that way.

So...  my current thinking is that a root component should be required to 
support an IConfigurationRoot interface.  Declaring support for an 
interface is a pretty strong statement that you know what making such a 
component entails.  And if you don't really know what you're doing, your 
component will probably break early on you anyway.

It does seem to me that there is a slight risk here of creating a new FAQ: 
"how come I can't look up this name?  When I use it in a binding.bindTo() 
it's fine, but if I use 'naming.lookup()' it gives me this error."  We'll 
have to make the error message for a missing configuration root suggest how 
to correct the problem.


>In essence, creating a "root" object is a bootstrapping job; application 
>code (i.e. code in classes and modules) shouldn't care.  Short scripts 
>will need to explicitly use the default root, e.g.:
>
>from peak.api import *
>
>root = config.defaultRoot()
>db   = naming.lookup("someDB://foo:bar@baz/spam", root)
>
>storage.beginTransaction(root)
>db('UPDATE parrot SET state="dead" WHERE breed="Norwegian Blue"')
>storage.commitTransaction(root)
>
>'config.defaultRoot()' is a proposed spelling; the actual name has not 
>been decided.  It may be that passing a special 'DEFAULT_ROOT' object to 
>the API's will tell *them* to look up the default root.  This isn't clear yet.

Currently I'm leaning towards requiring you to retrieve the default root 
and use it explicitly.  I think this will be less ugly to use than passing 
a singleton, and will require less internal change to PEAK.  (I'll need 
only to get rid of the default 'None' value for the appropriate API 
parameters.)

There is one other terminology/API spelling issue.  Should we speak of 
"roots" or "applications" as the primary terminology?

Root is technically correct, but application is more relevant to intended 
use.  The code example above seems more natural to me when written this way:

from peak.api import *
app = config.getDefaultApp()
db  = naming.lookup("someDB://foo:bar@baz/spam", app)

storage.beginTransaction(app)
db('UPDATE parrot SET state="dead" WHERE breed="Norwegian Blue"')
storage.commitTransaction(app)

However things then begin to veer into strange territory when it becomes 
necessary to discuss the "system app".  OTOH, if you always make your 
objects children of the default app, then until we have some kind of 
multi-application server, this issue is irrelevant.

The other thing I like about it is that it makes sense to say that "to use 
the configuration system, a component must be part of an application.  That 
is, it must have an application as its root component."  Then, we can say 
that "an application is a root component that implements 
config.IApplication", and that the easiest way to get a useful application 
is to use 'config.getDefaultApp()'.

Multi-threading is an interesting issue here.  It's rather tempting to make 
'getDefaultApp()' return a different app according to what thread you're 
in.  In practice, I think that running multi-threaded applications with 
PEAK is too open-ended of a question right now to know what the right thing 
to do is.

So what of the "system" application?  Is there a better name for it?  The 
only reason we need it is to support naming lookups in the context of 
classes.  And there we only need it if a URL is used in a 
'referencedType'...  Maybe we could get rid of the "system" application 
altogether!  I'm not currently aware of any utility or property lookups 
that are done in the context of a class or module object, and it's not 
generally useful to do it.

Right now the only thing we do with "foreign" components like modules and 
classes is relative name lookups.  Those are safe to use in any component 
tree, and don't depend on configuration lookups.  Granted, if the relative 
name lookup fails, it will probably cascade to an attempt to use the 
default naming context, which will fail because there are no properties 
available to specify the context.  But that could be handled by having the 
lookupComponent function not fall back in that case, and just throw a 
NamingError without bothering to try the default naming context (since 
there won't be one).

Okay...  so if we don't need a "system" application, do we need a "default" 
application either?  Hmmm...  maybe not.  If we can create an app at the 
drop of a hat with  'config.App()', what difference is that in practical 
terms?  The only thing saved is multiple loads of 'peak.ini'.  And, if 
you're creating a sensible application, *only* a __main__ module or script 
(or a test suite) should be creating app objects.

Wow.  Explicit really *is* better than implicit.  This is the exact same 
thing I was just saying on ZODB-Dev about the 'get_transaction()' machinery 
in ZODB.  In any kind of reasonable component architecture, singletons 
suck.  They don't reduce complexity, they add to it.  PEAK has been 
laboring under a lot of extra complexity to support the "special" 
configuration objects.

Interestingly, once the need for the singletons goes away, so does the need 
for a special declaration of "root".  We're back to where it's okay for any 
object to be a root; it just won't be usable for naming system lookups, or 
anything else that relies on a peak.ini-supplied default.  So if you 
accidentally make a root, it won't fail silently.

But, it might be confusing to simply "not find" properties or utilities, or 
to get an error message that suggests something is wrong with the naming 
system.  So I still think that the IConfigurationRoot interface is a good 
idea.  If a configuration lookup "falls off the top", and the top doesn't 
support IConfigurationRoot, we could issue a warning or an error specific 
to that.  Perhaps, if it's something that would result in a non-error, like 
a property lookup with a default, we should issue a warning, and for a 
property lookup without a default we would go ahead and raise it as an 
error (since the lookup would fail anyway).

Nah.  Probably we should just issue an error in all cases; it'd be better 
to fix a problem like that early rather than leave it dormant.

Hmm.  That was a lot of rambling in this message.  Guess I'll write another 
one summarizing the actual decisions from this one, leaving this as a 
record of the rationale.




More information about the PEAK mailing list