cocoon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Berin Loritsch <>
Subject [RT] Managing Dependencies in a Component Based Environment (long)
Date Wed, 26 Feb 2003 15:38:45 GMT
In this random thought, I want to address the issue of change in a
component based environment.  The reason I am bringing it up is because
of some of the issues that are present in Cocoon's CVS structure with
dependencies on dependencies on dependencies, and the issues that the
Avalon team faces daily as they try to make their projects better.
Through this excersize, we will hopefully learn just what amount of
change is really possible in a component based environment.


For the uninitiated, components are *supposed* to allow for greater
degrees of change under the hood.  The do this by all components being
loosely coupled.  Each component that requires another component uses
that other component according to the contracts put in place in its
interface.  While not all contracts can be effectively expressed in
code, they can be expressed in the javadocs.

Components live inside of a container.  THe container's responsibility
is to create, manage, map, and destroy all the components it controls.
As a result, there are a great many things that a container can
incorporate into its management of components.  Things like pooling
and caching can be done at the container level, as opposed to the
component level.  That's the theory anyway.


For developers who are not used to component programming what inevitably
happens is that component contracts are either incorrectly specified,
or because the user changes an implementation they want to have a new
interface as well.

Components are really cool in that they will allow you to have vastly
different implementations, all of which will work without breaking the
system.  As long as the contracts surrounding the component interface
are flexible enough to allow new implementations, yet strict enough
to force them to be compatible, we won't have any problems.

What happens if for some reason the interface changes?  For things as
simple as a package change (the actual binary interface is in all other
ways identical) the change can be handled by a dynamic proxy.  It's
really simple and all existing code still works.  For other situations,
the new component may still offer the same *role*, but with new
semantics.  A common and flexible solution is to provide a manual proxy,
or a facade component.  The facade component acts as a placeholder for
all requests to the old component and translates them into requests for
the new component.

The facade pattern is a powerful tool if used wisely.  You can play with
the interface until you find something that *works*, and then change
all the underlying implementations to the new interface.  That takes
care of the back end.


A challenge to frameworks that have been around for a while like
Cocoon is that other users outside of that framework's control use it.
It is a good thing until you have to change something.  So how do we
manage that change?

The facade component can be a useful tool to keep around, that will
nag the user at runtime until they change their code.  You don't have
to be as heavy handed at that, but the facade component can work to
maintain backwards compatibility as well as it can help the change
toward a new system.

Components can change more dramatically behind the scenes than
you might think.  As long as a component supports the client/component
contracts it is compatible.  Everything else behind the scenes can
change and everything still works.


Something in the pipeline for the future of Avalon is allowing the
developer to express meta information about the components.  What this
meta information allows is interaction with the container in a more
automatic way.  Caching hints can be expressed as component attributes,
and the container can interpret those attributes and provide the right
level of caching.  The caching implementation can vastly change, but
the component won't have to change its implementation.

The example of "component caching" is a bad example mainly because
it is *resources* that should be cached.  Components are simply meant
to be used.  However, it is a reality that Cocoon deals with.  Each step
in the pipeline can be cached.  That is a good thing, but the cache
was applied to the component and not the resource that it produces.
This choice was mainly because all the resources produced by the same
component usually have the same caching attributes.  It is a "shortcut"
so to speak.

The problem is that Cocoon expressed in component interfaces what is
really meta information.  In their defense, at the time Cocoon was
being written, not even Phoenix had the notion of meta information.
They did what they could do given the limitations of their container.
This is a bigger problem than simply changing a client interface, or
something along those lines.  It can still be handled by a facade
component that makes the translation from the old caching artifacts
to the new ones.


It is important to keep the core API of a framework as simple as
possible.  That is why I advocate the use of an API JAR and an
implementation JAR.  The API jar has all that is needed to properly
express the client/component contracts in a framework.  The
implementation JAR has all the blessed implementations of the
components.  When you need to support legacy users, you can place
all the facades and deprecated APIs in a separate compatibility JAR.
Both the interfaces and the facade implementations are included in
that JAR.  The users that don't want to keep up with change can
simply include that JAR and everything would work as it has in the

That would allow the Cocoon system to clean itself up, and separate
alot of dependencies.  The chalange is of course to make the
compatibility JAR completely separate from the original source.

If I am missing something, let me know.

View raw message