[TransWarp] Explicit is better than implicit
Phillip J. Eby
pje at telecommunity.com
Thu Apr 24 21:53:12 EDT 2003
Currently, in PEAK, when you do not specify a parent component for an
object, it is considered a "root" object. However, such "root" objects
still acquire configuration from two special objects: the default
AppConfig, and the SystemConfig.
Ty and I discussed today whether this is such a good thing. For "short
scripts" (and tutorials), it seems better to not have to explicitly specify
parent components. In this case, having an "implicit parent" of the
default configuration objects makes it easy to write an application without
needing any classes.
But, in a complex application, if one forgets to specify a parent component
while creating an object, it is entirely possible to end up with an
unintended "root", that does not follow the configuration of its usage
context. For example, the code below does that:
myApp = MyAppClass( thingItNeeds = SomeOtherClass() )
In the above example, 'myApp.thingItNeeds' will be a root object, even
though the obvious intention is that it should be a child of 'myApp'.
This specific use case can be handled by changing the code of the default
constructors to "suggest" a parent component to keyword arguments. This is
more implicit behavior, but as far as we can tell it is non-damaging: there
are exceedingly few use cases for intentionally creating multiple root
components within a single application.
Of course, adding this one implicit rule will not fix a host of other
circumstances wherein one could unintentionally create another root
component. And we cannot reasonably add code to trap all of those
circumstances.
So what we've discussed doing instead, is to make it so that unintended
root components will "fail early and often", drawing one's attention to the
error. To do this, however, we must make certain things explicit that used
to be implicit, and change some of the "rules of acquisition".
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'. 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)'.)
If you perform a configuration lookup on a component hierarchy that does
not "top out" to an explicitly designated root, you will receive an
error. This means that under most circumstances, an inadvertently created
"root" object won't be very useful and you will detect it quickly.
Note also that explicitly designated "root" objects will *not* acquire
configuration from outside of themselves. If you create an explicit root,
it will need to load base configuration data from 'peak.ini' (or wherever
you prefer) in order to function. We will probably provide some helper
functions or mixins to make this easier to do, but 99% of apps shouldn't
even worry about this. Instead, they should set up their main application
component as a child of the "default root" supplied by PEAK.
Indeed, now that we have finished the first version of the
'peak.running.commands' framework, it should no longer be necessary for you
to worry about this for most applications. The preferred way to boot an
application now is to create a class that implements 'ICmdLineAppFactory',
and invoke it via the 'peak' startup script. The startup process will
automatically create an instance of your application as a child of an
appropriate parent component. (This will also be forward compatible with a
hypothetical "PEAK Container" appserver that can run multiple applications
simultaneously; each app would be started with a completely separate
component hierarchy, having no way to interfere with each other.)
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.
There are indeed many further details to how this will work, but I do not
yet know what they are. At this point, I'm looking for feedback,
questions, etc. on this direction. In particular, feedback on whether the
explanation of the "new" model is clear, whether this is what you thought
about how it works now, etc. Thanks.
More information about the PEAK
mailing list