You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@avalon.apache.org by Leo Simons <le...@apache.org> on 2003/04/18 22:07:31 UTC
[RT] extending and modifying the container-component contract
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
examples:
* 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'''
----
setupFortress()
{
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
well.
== 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
g'night,
- Leo
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@avalon.apache.org
For additional commands, e-mail: dev-help@avalon.apache.org