avalon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Berin Loritsch <blorit...@apache.org>
Subject Recapping Discussion on Component Accessing
Date Mon, 14 Jan 2002 13:37:03 GMT
Now is the time to review a summary of how the Components are accessed in a
system.  There have been alot of ideas thrown around and clarifications.  In
the end, I have come to a couple conclusions:

1) There is still confusion over what constitutes a Component (I will have to
    write a book it seems ;P ).
2) Some people are not satisfied with the current simply role based access.

We have all agreed that Poolable is an interface we want to remove.  Component
is one that most people want to remove--and my reservations concern people
abusing the concept of a Component.  The SingleThreaded and Threadsafe discussion
provided a couple of solutions.

There are two approaches that have been approached:

1) Merge CM and Context
2) Use Context as CM and create a new ServiceManager

However, the issue I see is one of muddying contracts.  What was proposed as a
ServiceManager is nothing more than a ComponentManager.  What was proposed as
the new CM was nothing more than a Context with a release() method attached.

What was not addressed to my liking was that of how a Component was to know
how to manage it's threadsafety concerns (whether one instance could be shared
accross all threads or it needed to have a unique instance per thread).

The approach of writing a "factory" for each component is time-consuming, but
it does address how to control the lifestyle policy of a Component.  The major
issue is one of interface.  For a general purpose container to be written,
the Factories would have to specify a naming convention (i.e. like URLConnectionFactory)
as well as use a predefined interface.  The following would be an example:

interface ComponentFactory extends ObjectFactory, Disposable
{
     setUp(Logger log, Context context, Configuration config, ComponentManager manager);
}

Any necessary set up to the ComponentFactory happens in the initial setUp.  Even
if a Component only uses one or two of the objects passed in, all must be specified
to allow automatic set up of an environment based on configuration.

In essence, however, we will have two layers of factories.  One to create the initial
object (for the pool or singleton implementations), and one for the overall component
access point.  If we have two implementations (one for ThreadSafe and one for SingleThreaded),
all Components only have to extend one of the implementations for their ComponentFactory.
This will lead to a number of classes that look like this:

class DataSourceComponentFactory extends ThreadSafeComponentFactory {}

class JaxpParserFactory extends SingleThreadedFactory {}

While this does lead to predictable results for whatever component we are needing,
it adds alot of clutter to a system.  Extending a class just to change it's name
seems wasteful to me.  However, it does get rid of the need for SingleThreaded or
ThreadSafe as marker interfaces.  (This is a simple reworking of the ComponentHolder
stuff in ECM).


Moving on to the Context ~ ComponentManager discussion.  I must admit that there are
some unique possibilities with this approach that cannot be done with the current
ComponentManager architecture.  By employing three or four layers of Context objects,
you can easily represent a system fully.  Those layers are:

1) Application
2) Workspace (not all systems need this, but it is equivalent to Servlet Context)
3) Session (conversational state accross request boundaries)
4) Request (temporary state for the life of one request)

However, if we are to access Components through this approach, we must consider thread
interaction.  The Application and Workspace layers act as a global context, and that
carries the same requirements of a ComponentManager.  However, the Session object has
needs that are in between global context and request context.  I will cover the context
objects in reverse order.

Most request models assume that a particular request is processed in only one thread
at a time.  This is true for both event drivent architectures as well as for thread
per connection architectures.  For each slice of time, a request is processed by only
one thread at a time--even though the particular thread handling the piece of the
request may change during the life of the request.  We can leverage that fact to hide
a token for releasing objects from a request.

In essence, the Request Context would be able to have an interface like this:

interface RequestContext extends Context
{
     void releaseAll();
}

Internally, the RequestContext would know that all Components requested by the Context
only need to be requested once (and cache the instance for the life of the request).
When releaseAll() is encountered, all Components requested so far would be released
by that token.  The Request Context would have to be passed with every call to a Component:

cocoon.process(myRequestContext, uri);

The process method would then use the myRequestContext to access what it needs:

Sitemap sitemap = (Sitemap) requestContext.get(Sitemap.ROLE);
sitemap.process( requestContext, uri );

Finally, the container that originally sent the request would call the releaseAll()
method, and it would go through a list of all Components requested, and release them
if they have not already been released.

Moving to the Session Context, it follows the same principles as the request object.
The only problem is that it is not outside the realm of possibility that two requests
may be processed for the same session at any one time.  As an example, consider a
resource that takes 20 seconds to generate and an impatient web user.  The web user
might make the once every 2 seconds until he sees a result.  That means there are
10 simultaneous requests for the same resource.

We cannot rely on the Context to hide the token to release the Components retrieved
for the request.  We also cannot cache the instances we requested unless we keep
track of the tokens for each request.

One way of managing this relationship is for the Session Context to generate the
RequestContexts used.  The Session Context would be able to assign the token to
the specific RequestContext--and the RequestContext would defer component lookup
to the Session Context.  When the releaseAll() is encountered on the RequestContext,
any Components used by that request are released on the Session Context.

Technically speaking, we can supply the same relationship to the workspace context.
In essence, we can set up a WorkspaceContext like this:

interface WorkspaceContext extends Context
{
     SessionContext getSessionContext( Object sessionid );
}

interface SessionContext extends Context
{
     RequestContext getRequestContext( Object requestid );
}

interface RequestContext extends Context
{
     void releaseAll();
}

If you ask about the ApplicationContext, that is taken care of by the simple Context
interface.  Each Component get() operation will defer to the parent context until
it is resolved.  It is highly unlikely that a requestContext would explicitly declare
new Components to be used and would only contain certain values to control the request.

For example, Each context layer is identified by an id--which can be used to resolve
component mapping.  For instance:

Object get( Object key )
{
     if (key.equals(Generator.ROLE))
     {
         return m_parent.get(key + this.get("uri"));
     }
     Object retVal m_map.get(key);

     if ( null == retVal )
     {
         return m_parent.get(key);
     }

     return retVal;
}

While this leads to great flexibility, and the ability to alter the specific component
based on the Context information, it also adds complexity that is not needed in every
situation.  It would be able to hide the token used for releasing Components, as well
as remove the need for a separate ComponentManager and Context.  It also allows the
Context to handle some mapping for you.

However, there are some caveats with it as well.  For instance, in many systems the
mapping logic is either handled by a parent, or program logic in a separate component.
Whether that Component should be a ComponentManager or not can also be up for debate.

The heirarchical Context approach does provide a unified view of a Context hierarchy
in a system.  However, it also is very server centric.  We all know that Avalon is
flexible enough to be used in many different contexts.  Not to mention, the hierarchical
Context appraoch is too complex for environments where a simple CM would do.

In a web environment, the approach would work very well when all you want to do is
something like this:

Pipeline pipeline = (Pipeline) sitemap.process( requestContext, uri );
pipeline.sendResponse( outputStream );

requestContext.releaseAll();

The sitemap takes care of the request processing logic to build the pipeline, and you
deal with the simple interface.  It is all about simplifying the API for the developer.

However, I believe the Context was meant to be a light-weight object, and this adds
considerable bulk to the semantics of the Context.  To this end, some might say that
we just need JNDI--but that is just too much IMO.

Perhaps the heirarchy I outlined is too domain specific.  I would like your comments
on it, as it does have some merits and some drawbacks.

-- 

"They that give up essential liberty to obtain a little temporary safety
  deserve neither liberty nor safety."
                 - Benjamin Franklin


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


Mime
View raw message