[Zope3-dev] Context and Component Lookup

Phillip J. Eby pje at telecommunity.com
Wed Aug 6 23:14:39 EDT 2003


At 05:23 PM 8/6/03 -0400, Jim Fulton wrote:
>Phillip J. Eby wrote:
>
> > just as a component that is "part of" a radio would expect to
> > receive its "power" utility from the radio it is composed of.
>
>If I borrow my neighbor's DVD movie,
>I'll play it with my DVD player and radio, even though it's still
>their DVD. :)

But you have now changed its context, in a way that I'd consider equivalent 
to moving a content object.  But, we were talking about *using* 
something.  To modify your analogy, I understand this to be more like you 
going to your neighbor's house to watch the DVD on his player.  While you 
are there, you should respect the rules of *his* household - i.e., context.


>>Maybe I'm misunderstanding what you mean by "use".  I can think of two 
>>kinds of use:
>>1) Present a foreign component, using local presentation rules
>>2) Invoke non-presentation operations upon a foreign component
>>#1 obviously requires that I control where presentation adaptation is 
>>done, but that's okay because the foreign component doesn't need to know 
>>anything about that.  For #2, it seems downright reckless for me to try 
>>to do some kind of hidden parameterization by changing the foreign 
>>component's operating environment while I'm calling it.
>
>Maybe the difference is that my components are connected soley
>through interfaces. Your connections tend to be more intimate.
>
>Generally, the dependencies are on interfaces that must be provided in
>either case. So I don't see anything "reckless" here.

And that makes it clearer that we aren't quite on the same page.  My point 
was that if the "foreign" component does a utility lookup on itself, and 
gets a different result based on who called it, that this isn't a good 
idea.  For example, this forbids the "foreign" component from caching the 
result of such a lookup between calls.


> > If I need to
>>parameterize the component, why not just make an instance of it that's 
>>attached to my environment, or else explicitly define parameters as part 
>>of the call signature?
>
>We're talking about sharing content components.  So we get a bit of content
>from S2 that we want to use in S1.  We may need to adapt it to something else
>we need. I see no harm in using our adapters.

Me either!  What I am saying, though, is that if there's code in S1 that 
does this, why can't the S1 code do so explicitly?  Here, S1 is the 
consumer of the adapter, so it makes perfect sense for it to use S1 as the 
lookup context.  What I don't want is for this to have any impact on *the 
S2 code's execution*, which is what I understood you to be proposing.


> > It seems like an invitation to debugging hell to
>>have things controlled by some kind of threaded global.
>
>And I've experienced debugging hell because objects didn't have the
>context I expected them to have.  With what I propose, the source
>of components is much easier to predict that the way things are now.

And if the context is explicitly specified, it'll be even easier to 
predict, right?  :)


>>But do you have any other use cases, besides presentation?  I don't.
>>But maybe there is something we don't agree on regarding what it means to 
>>"use" a component?
>
>Well, I might have a component for converting between formats.  The two sites
>might have different components.  I want to use the converter for the site
>I'm using the object in.

That's cool, as long as it's the *consumer* of the foreign component that's 
doing the lookup, and saying where to look it up from.


>   Also, I'm moving in the direction of unifying many
>of the component types.  For example, a view is just a named adapter from
>an object and a request to some Often empty) interface.
>
>I can think of a pretty comelling example that supports your point of view,
>however. If I modify an object from S2 in S1, I probably want the events to go
>to S2.  Then again, it's likely that S1 and S2 will share a common event 
>service,
>so it won't matter.

That's the kind of thing I'm talking about, yes.  Interference with an 
object's lookup context is "invasion of privacy" aka breaking 
encapsulation.  The object's author knows more than you do about what his 
object needs/wants.

I am saying there should be "freedom of choice" (objects that do lookups 
can choose where they want to look up from) but no "invasion of privacy" 
(other objects should not have the ability to reroute an object's lookups, 
unless they are the object's owner or context, or have been explicitly 
delegated this responsibility in some other way).



>>>Sure. That's just an example. The point is that there are many components
>>>that don't have containment information, many of which don't really have
>>>a place.
>>
>>But why do they need one?
>
>We need a basic for looking up components.  If the component's place is the
>basis for looking up other components and the component doesn't have a 
>place, then
>that's a problem.  OTOH, if we always look up components based on the place we
>initiate the computation, we have an unambiguous place to do the lookup.

Okay, let me see if I understand.  What you want, is that code that calls 
Z3CA functions like getAdapter, getUtility, etc. should not have to know 
what "context" to use.  Or, in other words, objects should be able to use 
the Borg's communication system without needing implants.  :)



>>>No, because the component lookup may be far removed from the
>>>presentation code.
>>
>>*confused look*.  I think I really need a real (i.e. motivating) use case 
>>to understand this.
>
>You have some presentation code that calls an operation on a content 
>component.
>This operation calls other operations, possibly on subobjects or objects
>returned from other operations.

I don't see any motivation here.  Why do I (presentation code) want to 
interfere in those objects' private business?  If they're not doing 
presentation operations, what business is it of mine?  Presumably, the 
objects are separated into presentation and non-presentation code for a 
good reason.  If the non-presentation objects need to call into 
presentation code, then there should be an explicit parameter passed, such 
as a subcriber for notifications.

What I mean by motivating use case, is a specific thing that some piece of 
Zope 3 code is currently doing, that is ugly because of the way things work 
now, and would be made better by having this implicit contextness -- and 
wouldn't be just as much better if objects could conveniently manage their 
own context, e.g. by an attribute pointing to their container.


>This is getting circular.  I don't want an object to need to be context
>aware to find components. A component should be context aware if *it*
>uses context itself in some way, as sometimes happens.  If component lookup
>requires context, then any content component that might lookup components
>must be context aware.  That's why I don't want component lookup to require
>context.

What I don't get, is why content components shouldn't be allowed to do a 
context-free lookup in that case.  If the object doesn't have any context, 
then presumably its author *wanted* it that way.  No implant, no Borg 
hyperspace communications.  ;)  Heck, it's not like there can't be an 
interface to implement, such that no DNA modification is required, just an 
external adapter.  :)



>>>  but I'm not comfortable
>>>requiring special DNA to work with components.
>>
>>...and then I don't get this.  What are you saying is the "special DNA" 
>>to "work with components"?
>
>If component lookup requires context and if a component wants to do component
>lookup, then it must be context aware. It must have the context awareness DNA.
>I don't want to require this DNA and, thus, I don't want to require 
>context for
>component lookup.

So, if you don't have context, use only global services.  Aren't most Z3 
services globally registered anyway?



>>(For that matter, I don't see why you think *proxies* are necessary for 
>>objects to have context.  Adapters are quite sufficient; for example in 
>>PEAK I have an adapter for ModuleType so that modules can be considered 
>>components which have their package as their context.)
>
>Adapters *are* proxies.  The proxies I'm refering to are also persistent,
>which is usually not the case for adapters.

By proxy I meant "thing you pass around everywhere *in place of* the 
original object, that is expected to conform to any interface the 
underlying object supports".  I.e. Acquisition wrappers, context wrappers, 
security proxies, the new decorators, etc.  An adapter, by contrast, is not 
expected to supply all the other interfaces of the object it adapts.


>>>I agree, but should context awareness be a prerequisite for working with
>>>the component model? I suggest not.
>>
>>I guess I'm confused on why you think this is required.
>
>If component lookup requires context, then context awareness is required to
>do component lookup.

This is the bit I don't get...  If somebody is *writing code to use the 
Z3CA*, and they're writing a *persistent object*, they've already shown 
themselves quite willing to join the Borg.  They're not going to object to 
a painless little implant.  :)  I think maybe you're assuming that because 
ContextAware is a PITA to use *now*, that this would still be true in the 
case of containergeddon or something like it, where objects just keep track 
of their "one true parent".

For example, suppose that being ContextAware meant nothing more than that 
you have to have a __parent__ attribute?


>>>That works great to the extent that we get to objects via traversal, but
>>>traversal isn't the only way to get to objects. We often get to objects
>>>via method calls. We have been forced in many cases to make the methods
>>>wrap their results.  This is a lot of bother.  It significantly slows
>>>development.
>>
>>*lightbulb goes on, briefly*  I think *maybe* I understand what you're 
>>saying, but let me see if I can paraphrase it:
>>1. You need to look up components in the context of some other component, 
>>either utilities, adapters, or "other".
>
>I'm not sure what you just said.  I have components, typically content
>components, for which I may need to look up adapters, and other components.
>I can't control whether these components for which I need other components
>have context.
>
>
>
>>2. Because components cannot be generally assumed to be context aware, 
>>*all* code ends up assuming a burden of supplying context to the objects 
>>that are being passed around.
>
>Yes, that's a way to look at it.
>
>
>>I think I've managed to avoid this problem in two ways...  first, I 
>>almost never look up utilities in the context of another object than self.
>
>That doesn't tell me anything. What's self? What't to assure that
>self has a context?

This is what clued me in to the fact that you're trying to save the Borg 
joinees from having to have implants.  :)  IMO, if I had to subclass 
Persistent and I'm importing stuff from zope.component, I've already joined 
the Collective.  Hook me up a __parent__ attribute or whatever so I can 
assimilate, already.  :)


> > Instead, the responsibility for providing the utility is placed
>>on the interface of the object that would be the context for such a 
>>lookup.  That is, utilities should be part of an explicit interface, or 
>>the use of them hidden within their client.  (The supplying component may 
>>then look up the utility in its own context, but the point is that it's 
>>the supplier's responsibility.)
>
>I'm sorry, I've read this about 5 times and it doesn't make any sense
>to me.

Let's say I come over to your house, and would like to consume some food 
from your refridgerator (access a utility).  It should either be part of an 
explicit agreement that I can take things from your refridgerator (and if 
so, what I can expect to find there), or we should agree on what food you 
will supply me with when I come over, and then you go and get it from the 
fridge for me.  :)

In other words, it's a little forward for objects to look things up in 
other objects' contexts.

(OTOH, I see the more worrisome issue of thread-based context as being like 
all your friends re-organizing your refridgerator whenever they come over, 
such that you can't find a darn thing whenever they're around.  You could 
reach in for a beer and end up drinking photographic development fluid, or 
something.)


>>Second, for adaptation, the caller is always in effect required to know 
>>the context for adaptation, because it is the caller's context that 
>>desires the functionality.
>
>You are assuming that the caller is aware of context in the first place.
>I want to avoid requiring that.

This is now clear to me.  :)


>>Now, it may be that these approaches can't work for Zope 3 for some 
>>reason; maybe there is a lot more need for objects to poke into each 
>>others' business that I'm not understanding.  If somebody can point me at 
>>some code that they don't believe this approach works for, it would help 
>>my understanding tremendously.
>>Specifically, a counterexample would be a situation where either:
>>1. An object A looks up a utility in context of object B, but it does not 
>>make sense to explicitly require IofB to provide the utility as an 
>>attribute or via a method, or
>
>No, an object need to look up a utility. For it's own use.
>Now, it calls getUtility(self, IFoo).
>
>Now suppose that the object isn't context aware. If that's the case,
>it will get a global utility.
>
>Finally, assume that all of this happened in a method of an object that
>was being used in some site that has an applicable utility, but the site's
>utility isn't used.  This is liklely to be surprising to someone.
>
>Perhaps there isn't a global utility, in which case the call fails even though
>there is a matching utility in the callers's site.

Understood; this is where we disagree, because I believe components doing 
lookups should know their own context, and that this is the natural price 
of being able to do a context-driven lookup.  Maybe it should be a cheaper 
price than it is currently in Zope 3; I know that my experience of doing so 
in PEAK is that it's a price more than worth paying, and a price that can 
also be lessened with adapters.  OTOH, I *don't* work with persistent 
application-level components (as opposed to application *data* components), 
so maybe there's a big difference there.  But I find it hard to see what 
the big deal with having a required attribute is, anyway, given that 
there's already such things as __implements__ and __provides__.


>>2. An object A gets an adapter (or view) of an object B, and it does not 
>>make sense for A to use itself, or some known context-aware collaborator, 
>>as the context for the adapter/view lookup.
>
>Well, lets consider some possibilities.
>
>Let's suppose A calls getAdapter(B, IFoo, context=A).
>Imagine A is from S1 and B is from S2.  Now we'll adapt
>B using an adapter from S1, which won't make you happy.

On the contrary, that's exactly what I think it *should* do, for an adapter 
lookup, although I'm not averse to it using B as the context, 
either.  Using B as context for a utility lookup, though, is a bit like 
being a pushy guest, grabbing a beer out of B's fridge without an 
invitation.  :)  If I am to be allowed to do that, it should be part of the 
documented interface (invitation).


>Or, perhaps A doesn't have context information, in which case
>we'll either get the global adapter, or we'll fail. At that point,
>probably no one's happy.

Ah, but it's not anybody else's business to make A work.  If A wants 
context, A should know *what* context A wants, or else we're just doing 
guesswork.  "In the face of ambiguity, refuse the temptation to guess".


>Now suppose instead, A calls getAdapter(B, IFoo). If B has no
>context, then either we'll get a global adapter or fail. Again,
>no one happy.

But then, why isn't it defined as a *requirement* that B have a context in 
that case?  Why can't I say, "hey, if you don't have beer in your fridge, 
I'm not coming over"?  :)  In my experience, though, it's pretty rare for a 
client to want to use a supplier's context for an adapter.  Presumably, I 
am adapting to meet *my* requirements, so my context would "know best" what 
adapter is suitable, no?

As I said, perhaps my experience doesn't mesh with what Zope 3 code 
actually does - and I'll be the first to admit my knowledge of day-to-day 
zope.app coding is very limited.  But I would like to understand *why* you 
would want to use the thing you're adapting, as the context for the 
adapter, and yet *not* make that part of your interface requirements for 
the object you're getting it from.



>If B does have context, we'll get the adapter from B's context.
>At that point, you'll be happy, but I probably won't be.

I doubt that I'll be happy, unless I had some special reason for wanting 
B's context to supply the adapter, and at the moment I can't think of any 
reason for wanting that.



>>In my understanding, it seems that #2 would almost always be resolvable 
>>to either a) it's a presentation-related adaptation/view, and belongs in 
>>a context based on the request,
>
>Do you mean that A is presentation code?

Yes.


>But what happens if A calls non presentation code? Should the rules
>change?

No.  If I want that non-presentation code to do something presentation-ish, 
I should supply explicit parameters.  If I want to adapt the 
non-presentation object in some way, I should use my context for the 
adapters, because I'm part of a context that has some intended way of 
presenting things.  If I use the non-presentation object's context to find 
presentation components, I could end up using the wrong presentation.

Now, if I need the non-presentation component to point me to some other 
non-presentation component, then of course I need its context, *or* make 
the information I need an explicit part of the interface between the 
presentation and non-presentation component.


>  or b) it's not presentation-related, and
>>object A should use itself (not object B) as the context for adaptation.
>
>So, suppose that we have a view in site S1. Any object it accesses
>should be adapted by adapters from site S1.

Agreed.


>If the view accesses an operation on a context aware object from S1 and that
>operation adapts an object from S2, then an adapter from S1 should be used.

Yes.


>If the view accesses an operation on a context aware object from S2 and that
>operation adapts an object from S2, then an adapter from S2 should be used.

Yep.


>If the view accesses an operation on a context aware object from S2 and that
>operation adapts an object from S1, then an adapter from S2 should be used.

Sounds good so far.


>If the view accesses an operation on a non context aware object and that
>operation adapts an object from S1 or S2, then a global adapter should be 
>used.

Right on the money.


>This all seems pretty arbitrary and likely to lead to debugging hell.

This seems to me like, "the object that wants something should decide where 
it wants to look it up from, and ordinarily an object wants to look things 
up with itself as the context".  Looks perfectly consistent across the board.


>>>Worse, developers usually don't understand why it's necessary.
>>>Something doesn't work, so they look into their book of incantations.
>>>The
>>>book suggests that maybe a context wrapper will work, so they offer one up.
>>>To them, it's a dead chicken.
>>
>>I think that will be helped a lot by making it explicit, and *not* a 
>>"transparent" wrapper.  If an object is expected to supply a context for 
>>something,
>
>Why must we expect an object to supply a context for something?
>Generally, the thing supplying context is traversal.
>We get objects lots of other ways. When a method returns a value, most of
>the time, all it cares about providing is the value itself. It doesn't
>*care* what you do with it.  Some methods provide context for their return
>values. Where this is done, however, it's to supply context for
>context-aware objects that use context for something else.

I think I'm better understanding what's at issue...  Z3CA lookups, apart 
from utilities, default to using the subject of adaptation as the 
context.  Whereas, for anything other than utilities, I prefer to use an 
explicit context (that rarely has any connection to do the subject of 
adaptation).

Because of this, I don't run into the issue you describe, because there is 
almost never any reason for me to *care* what context an object returned by 
some arbitrary method.  To me, that object's context and how it does 
lookups is its own private business and nothing to do with me.  Thus, most 
code in PEAK can be "context agnostic" when dealing with *other* objects, 
even if a particular object needs to know its *own* context in order to 
accomplish certain things.

What I'm curious about here, is whether we differ over a style issue here, 
or whether there are Z3 requirements that prevent you from using the same 
approach.  It may just be a style issue, in that you've never seemed 
particularly keen on encapsulation, while I on the other hand can be 
incredibly persnickety about it.  :)


>I don't think transparency has anything to do with this.  However,
>I think I may be getting a glimmer of what you're suggesting.
>
>I suppose you'd want the context to determine the policy for how to look
>up a component. So, for example, the implementation of getService might look
>something like:
>
>   def getService(context, servicename):
>       sm = getAdapter(context, IServiceManager)
>       return sm.getService(servicename)
>
>so that the context could determine the policy for looking up components.
>Of course, there's a recursion problem here. The adapter would need to be
>provided globally, via __conform__ or via having the context *be* an
>IServiceManager already.
>
>Is this the kind of thing you're talking about?

Nope.  My desire is to clarify responsibilities between objects and make 
context-driven coupling more explicit, instead of more implicit.


>Even with this flexability, I'm not sure how you'd use it. :)

I *do* implement it something like that in PEAK, but it actually iterates 
over an object's parents, asking them for whatever it's looking for and 
skipping those that don't know anything.  The useful bit about this is that 
objects don't have to be service managers in order to be part of a chain, 
they just have to know how to be part of the chain.  And that can be 
supplied by adapters, for many useful objects.  For example, a GUI window 
can consider its parent window to be its context.

Anyway, this doesn't speak to your current issue, so let me make a more 
concrete suggestion: why not wait till after containergeddon before you 
decide to use thread-local variables?  If context-awareness can be made 
sufficiently trivial, you might not *need* to do anything else.

I think one other factor that may be influencing the perceived complexity 
of context-awareness, is that right now context doesn't persist.  Perhaps 
many of the situations that now require a "viral" spread of 
context-awareness, are caused by the fact that the objects being passed 
around don't know what their "one true context" is.

Pretty much all context in PEAK comes from one of two places:

1) an object is given a context by its creator, and the context then stays 
with it until the object is dead.

2) an object is created by a factory of some kind which leaves it 
contextless until one of its consumers "suggests" a context for it.  It 
then keeps that context till it dies.

If containergeddon allows objects to keep track of their context, then I 
would think it would be possible to have the same principles apply.  Notice 
that when such an object is just being passed around by other objects, 
there is no need for them to manipulate or even care about what the 
object's context is, and so should stem the tide of "contagious 
ContextAwareness" that I understood to be motivating your proposal.




More information about the Zope3-dev mailing list