avalon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Leo Simons <leosim...@apache.org>
Subject [RT] extending and modifying the container-component contract
Date Fri, 18 Apr 2003 20:07:31 GMT
Hi gang,

= The stability commitment =

for the foreseeable immediate future, we are not going to be slapping 
deprecation markers on stuff like Context, Configuration, ServiceManager 
and the lifecycle interfaces that belong with those. These classes form 
the core of the core of the core of many deployments. We might add one 
or two methods here or there, but I think we won't be removing them or 
even changing method signatures anywhere for the life of avalon 4.

This is one of the reasons why we have container-specific extensions to 
the container-component contract. The other reason is that those 
extensions cannot possibly be considered general to all containers.

= Overview =

I'm going to explore some of the common ways the container-component 
contract is extended in various containers. First up is a look at the 
core art of avalon-framework: the component lifecycle. This is mostly 
stating the obvious for the hardcore avalon devs, but I'm sure everyone 
can learn a thing or two about at least one container :D

= The lifecycle =

== Additional lifecycle stages ==

In many problem domains, there is need for both the creation of new 
lifecycle stages (re: Instrumentable in avalon-excalibur). While it 
almost always is possible to (ab)use existing stages for the same goal, 
the system is sometimes cleanest when using what we have dubbed 
"lifecycle extension".

A package to facilitate implementation of additional stages in a 
pluggable way is avalon-lifecycle, used in fortress and merlin, and soon 
to be released. It is not actually neccessary to use this package to 
support additional lifecycle stages. One can also imagine a non-generic 
way, something like

'''example of a non-generic way to implement lifecycle extension'''
  class SpearheadContainerUtil extends ContainerUtil
    void instrument( Object obj, InstrumentManager im )
      if( obj instanceof Instrumentable )
          ((Instrumentable)obj).instrument( im );

  class SpearheadComponentEntry
    doStartup( Object c )
      SpearheadContainerUtil.enableLogging( c,
          m_logManager.getLogger( c, this ) );
      SpearheadContainerUtil.instrument( c,
          m_instrumentManagerManager.getInstrumentManager( c, this ) );
      SpearheadContainerUtil.contextualize( c,
          m_contextManager.getReadonlyContext( c, this ) );
      SpearheadContainerUtil.service( c,
          m_serviceManagerManager.getReadonlyServiceManager( c, this ) );
      SpearheadContainerUtil.configure( c,
          m_configManager.getReadonlyConfig( c, this ) );
      SpearheadContainerUtil.initialize( c );

or whatever fits best with the internal organisation of a container 
implementation. There is utter lack of headache here. One can simply 
state in the feature description "Spearhead provides full support for 
the hosting of Instrumentable components as defined in 
excalibur-instrument", and that's crystal clear.

== Additional semantics for existing lifecycle stages ==

Sometimes its desirable to attach additional meaning and/or contracts to 
an existing lifecycle stage. The most simple one has to do with 
suspend/resume and Re*, and is implicit: most containers effictively 
guarantee this subpart of the cycle will be called 0 times, not 0 or 
more times.

The two other stages which have been the subject of most use, debate, 
and contract modification are contextualization and servicing. It almost 
seems like no container completely does not touch these stages. Some 

* Phoenix guarantees that the Context passed in will be an instance of 
BlockContext, an interface which allows a hosted component to learn more 
about its environment, like the current working directory, and which 
also functions as a callback hook to the container (providing a 
requestShutdown() callback, for example).

* EOB does much the same thing, providing getBeanDirectory() and 
getApplicationDirectory() as convenience methods in a specialized 
BeanContext interface.

* Plexus provides a specialization of ServiceManager, which allows you 
to retrieve components with a specific role/id pair, meaning you can be 
sure to always retrieve the same specified component on multiple lookup 
calls. In addition, it allows you to specify a configuration element to 
the lookup, meaning a component has a way to specify dynamically the 
additional features it wants a component to provide.

* Plexus provides a specialized PlexusContext which allows convenience 
access using methods like getWorkDirectory(), and also allows elaborate 
callback through getPlexus().

* Merlin merges the contextualization and servicing concepts (behind the 
scenes, that is). Basically, everything which goes into the context can 
also go into the ServiceManager and the other way around. In addition, 
it extends the Context interface in quite a different way, namely by 
adding release() semantics to it. This results in a generic Map-style 
object being passed around.

* to make the picture more complete, its worth noting that neither 
Fortress nor ECM provide a specialized context, or really any kind of 
default or callback context. They simply pass on the context you feed 
into them onto the components they manage. This means that you can 
implement callback and stuff like that by putting in place a reference 
to the container itself into the context, ie

'''a callback context using fortress'''
    DefaultContext context = new DefaultContext();
    context.put( "working-directory", m_workingDirectory );

    FortressConfig config = new FortressConfig( context );
    // put some stuff into this config

    m_containerManager = new DefaultContainerManager(
        config.getContext() );
    ContainerUtil.initialize( containerManager );
    m_container = (DefaultContainer)m_containerManager.getContainer();

    context.put( "fortress-callback", m_container );

    m_container.addComponent( /* ... */ ); // will have a callback option

== Additional semantics by contract ==

There's a whole range of contracts encoded in the lifecycle handling of 
the various implementations. An example of such a contract is the 
handling of a role ending with <tt>/Selector</tt> by a ServiceManager, 
which according to the framework contract needs to return a 
ServiceSelector, something nicely encoded in the 
<tt>AbstractContainer.get()</tt> method inside Fortress.

* Phoenix introduces additional semantics for ServiceManager which allow 
the retrieval of maps and arrays (tacking on [] at the end of the ROLE) 
of dependencies from the ServiceManager.

* Phoenix and merlin read xml configuration files which describe what 
the component expects to be present in the Context and/or ServiceManager 
which will be passed in, and won't set up the component if it requests 
something the container does not know how to provide. This is what we 
call "validation" of component requirements.

* several containers guarantee that some context entries will always be 
present. Plexus, for example, makes everything which is available by 
convenience method of PlexusContext available by using Context.get() as 

== Comparison of the methods of adding semantics ==

The downside to container-specific semantics is that a component will 
not run in other containers when it depends on those semantics. The ease 
with which various additional semantics are provided depends on what 
exactly those additional semantics are.

As pointed out above, something like an Instrumentable interface is 
something which is easily added to any neatly designed container.

Availability of context entries is also often easily done. For example, 
supporting Context.get( "plexus:work" ) within a fortress container is 
easy to accomplish by providing it in the context passed into the 
fortress container instance. Similar arguments hold for ServiceManager, 
of course.

It can often be a lot more difficult to support additional semantics 
which are encoded in the form of specializations of the arguments to the 
standard lifecycle interfaces, since you likely tie your components 
directly to a container-specific interface if you use them 
(BlockContext, BeanContext, ServiceBroker, ...), and it may require 
significant reworking of a container to support those.

== Opinion ==

It would be wise for all container developers to always keep this in 
mind, and at least make sure to support the "soft" way of depending on 
container-specific semantics (which doesn't involve casts to specific 
interfaces) in addition to the "hard" way, with PlexusContext being a 
nice example. When this is done, the component developer has a choice.

For component developers, it is always wise to try and make do with as 
little container-specific semantics as possible. In particular, we 
should strive to make sure that components hosted at avalon are soft on 
container needs. Since I've found that fortress introduces the least of 
the container-specific semantics, it would be a good idea to verify all 
avalon components we host can be successfully run in fortress.

Finally, this is not intended as a rant against container-specific 
semantics. Some of the provided features have been proven to be 
immensely useful (like, in my experience, the plexus ServiceBroker and 
the phoenix dependency arrays)!

== Wrapping up ==

The Avalon-Framework lifecycle provides a basic common ground for all 
containers, and the container-component contract it specifies is quite 
usable. It is not always adequate though, and many container- and/or 
application-specific to the lifecycle exists. I've given an overview of 
some of those. Note the wiki format this doc is in. I challenge someone 
who finds this overview useful to do the neccessary copy-paste :D


- Leo

To unsubscribe, e-mail: dev-unsubscribe@avalon.apache.org
For additional commands, e-mail: dev-help@avalon.apache.org

View raw message