[PEAK] "Global" Services and Service Areas (was Re: peak logs)
Phillip J. Eby
pje at telecommunity.com
Fri Jan 2 18:13:15 EST 2004
At 02:51 PM 1/2/04 -0500, Phillip J. Eby wrote:
>It seems a simpler solution would be some way to say "I'd like to have
>this component be used as a home for global services." The hard parts
>are: 1) which component? and 2) what services?
>
>In practice, most breakage today comes from configuration files loaded
>into non-root components, so that would be an obvious place to start on
>part 1. But part 2 is not nearly as obvious. Some components *want* to
>stay global. For example, my hypothetical ZConfig schema service.
>
>I'm going to have to think about this one some more. At least for now,
>fixing it is easy when you know what's happening: just add a Component
>Factories declaration for the affected services.
Ty and I just spent some time on trying to find a "global" solution to this
issue. Here's what we came up with.
In the real world, components have a "box", with things that are "inside"
and things that are "outside". One explicitly connects things on the
"outside" of the component. For example, if I bring a toaster home from
the store and put it in my kitchen, it doesn't plug itself in. I have to
do that, explicitly.
But on the inside of the toaster, there is likely a wiring bus, where
certain services (such as "power") are made globally available, and thus
implicitly connected.
Right now, PEAK treats everything under a given component root as a single
undifferentiated mass of components. Everything is both explicit and
implicit. Every component is a box, and yet no component is. Thus, it is
easy to miswire a component when you provide a new source (offerAs, .ini
file) for a signal on a wire, and that same wire is used for another source
higher up on the "bus". We then end up with a short circuit, as in
Darryl's problem today, or my gaffe a few weeks ago in giving the
Supervisor tool a private reactor, without also giving it a private mainloop.
So it seems we need components to be able to "express their
boundaries". In a sense, we draw a "box" around a collection of components
that desires to be "self-contained". Service lookups performed by
components "inside the box" must be satisfied by a service that is also
"inside". Right now, when you use [Component Factories], you're declaring
a single global default instantiation of that service. What we'll be
changing to, is that [Component Factories] are "one instance per box",
instead of "one instance per declaration".
In practical terms, what that means is we'll "draw a box" around
configuration files loaded by 'runIni', so that the .ini file is treated as
a self-contained app. It'll still inherit configuration from its parent
components - even the *definition* of component factories. It's just that
the "box" will have its own *instance* of such components. So, this would
produce the same effect as the workaround I gave Darryl; it'll be as though
all the [Component Factories] sections of parent .ini files were
automatically copied into his .ini file.
Technically, the way we will "draw a box" around a component is to have it
implement a config.IServiceArea interface. In the common case, this will
probably be by having it subclass a 'config.ServiceArea' base
class. IConfigurationRoot will extend IServiceArea, so a configuration
root will implicitly be a "box" (service area), just as it effectively is now.
Few programs or components will need or want to do this,
though. Essentially, you'll do it only when you're overriding something
defined by the next "service area" up the component hierarchy, and want to
ensure that any other "global" components you or your children use will
respect your override. The process supervisor tool needs to do this
because it absolutely needs its reactor to be local, to avoid conflict with
a reactor used by its child processes. Thus, it can be, and should become,
its own service area.
Creating a service area essentially means that you can't access services
outside that area, without explicitly connecting them -- just like you
can't get power to the toaster unless you plug it in. This means, for
example, that if I needed the process supervisor tool to use an
externally-provided transaction service, I'd need to explicitly register a
way to get it from outside the service area.
On the whole, this change should make it a lot harder to mess up
runIni-based apps' configurations, although you'll still have to bear in
mind that loading configuration files into a service area's child
components will not affect the services provided there. It should also
make it easier to intentionally "box in" a set of components, as I had
intended to do with the process supervisor.
Finally, it should make it a little easier to understand where certain
kinds of configuration take effect. If you want a setting to affect any
"global" services, you must apply the configuration directly to the
appropriate service area, rather than to a child component.
I'm bumping this change to the head of the line for alpha 3, because pretty
much everything else I'm working on for PEAK involves adding more global
services, and therefore more opportunities for these sorts of errors.
More information about the PEAK
mailing list