avalon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Leo Simons <leosim...@apache.org>
Subject context usage patterns
Date Sun, 24 Nov 2002 00:15:44 GMT
Hi gang,

(new subject line, same thread)

there's a few different approaches regarding component architecture
decomposition. Recently we've been basically re-doing old discussion on
this, which is good as I'm having fresh thoughts about how to explain
what I think is best. Andy, if you're reading, here's example-based
explanation :D

Several approaches to using the avalon Context:

1) published keys, cast on lookup
=================================

sample code
-----------
public final class MyCommonContextKeys
{
    /** java.io.File where we can store permanent data */
    public static final String HOME_DIRECTORY_FILE =
            "avalon:home-directory";
}
public class MyComponentImpl implements Contextualizable
{
    contextualize( Context c ) throws ContextException
    {
        // if a component were able to specify by contract what it
        // will look up and what it will cast that to, the try/catch
        // is not necessary.
        try
        {
            final File homedir = (File)c.lookup(
                    MyCommonContextKeys.HOME_DIRECTORY_FILE );
        }
        catch( Exception e )
        {
		throw new ContextException("Where's my home dir????!?");
        }
    }
}

Requirements
-----------
- Well-published and agreed upon context key definitions
- either the component developer must do one of:
  o specify in some way what a component requires from the context
    (ie requires meta model, value of meta model is biggest when shared
    across the board). This becomes:

// ...
    contextualize( Context c ) throws ContextException
    {
            final File homedir = (File)c.lookup(
                    MyCommonContextKeys.HOME_DIRECTORY_FILE );
    }
// ...

MyComponentImpl.profile example
-----------------------------
<!-- ... -->
	<requirements>
		<context>
			<entry  key="avalon:home-directory"
				class="java.io.File"/>
		</context>
	</requirements>
<!-- ... -->

  o use lots of try/catch (and people reusing the component must then
    figure out what goes wrong from log messages); this becomes:

non-supporting-container.log
----------------------------
#...
[ERROR][MyComponent] 12-01-02 14:34 contextualize() threw an Exception:
"Where's my home dir????!?"

  or the container developers must agree on specifying a contract
  stating what a container should provide in the context (limiting
  reuse and grinding down container evolution); this becomes:

class Context
{
	File getHome();
	File getWork();
	Blaat getSuch();
}
MyMycroContainer implements CrappyAndTooHeavyForUse {}




2) extension of Context class
=============================

sample code
-----------
public class MyExtendedContext
{
    public File getHome()
    {
        // some kind of implementation needs to go here
    }
}
public class MyComponentImpl implements Contextualizable
{
    contextualize( Context c ) throws ContextException
    {
        if(! c instanceof MyExtendedContext )
            throw new ContextException("I need a MyExtendedContext!!!");

        final MyExtendedContext mec = (MyExtendedContext)c;
        final File homedir = mec.getHome();
    }
}

Requirements
------------
either
- all containers use the same extensions (which again leads to
MyMycroContainer implements CrappyAndTooHeavyForUse {})
or
- component developers write different versions of their components for
different containers, which is a maintenance problem:

class MyBasicComponent
{
	// blaat
}
class MyComponentForContainerA extends MyBasicComponent
{
	// different behaviour based on different options in the context
}
class MyComponentForContainerB extends MyBasicComponent
{
	// different behaviour based on different options in the context
}
// ...

it will always result in

non-supporting-container.log
----------------------------
#...
[ERROR][MyComponent] 12-01-02 14:34 contextualize() threw an Exception:
"I need a MyExtendedContext!!!"

regardless of the option chosen. It also means work for each and every
container developer.

3) embedding of extended context class
======================================
Very similar to 2) except it also needs a context key registry.

sample code
-----------
public final class MyCommonContextKeys
{
    /** to get at MyExtendedContext */
    public static final String MY_EXTENDED_CONTEXT =
            "extension:my-context";
}
public class MyExtendedContext
{
    public File getHome()
    {
        // some kind of implementation needs to go here
    }
}
public class MyComponentImpl implements Contextualizable
{
    contextualize( Context c ) throws ContextException
    {
        // if a component were able to specify by contract what it
        // will look up and what it will cast that to, the try/catch
        // is not necessary.
        try
        {
        final MyExtendedContext mec = (MyExtendedContext)c.lookup(
                MyCommonContextKeys.MY_EXTENDED_CONTEXT );
        final File homedir = mec.getHome();
        catch( Exception e )
        {
            throw new ContextException(
                    "Where's MyExtendedContext????!?" );
        }
    }
}

Requirements
------------
- Well-published and agreed upon context key definitions
and either
- all containers use the same hierarchy of context objects
or
- component developers write different versions of their components for
different containers

My Take
=======
The first option (which is also the one currently advocated by the
meta/info model stuff and also by current practices if you ask me) is
clearly the winner, provided we get to a common meta model. ATM we don't
have a common meta model, rather de facto and undocumented ones for each
container (which was not a problem back when ECM didn't do any of this
and phoenix was the only player, but is now and will become in the
future), meaning component developers either need to 'develop
defensively' (with try/catch) or decrease the robustness of the
component.

Following this approach also requires container developers specify
clearly which context entries they can and cannot support (something
like

my-container.log
----------------
[container][setup] VERIFICATION WARNING: the profile for MyComponentImpl
specifies it depends on a java.io.File in the context for lookup key
"avalon:home-directory" but this container doesn't know about the
contract surrounding this context enty.

[container][MyComponentImpl] CONTEXTUALIZATION WARNING: MyComponentImpl
is looking up "avalon:unspecified-attribute" but this is an undeclared
dependency. Do you need to modify MyComponentImpl.profile?
----------------

Responsibility for a lot of this is then in the container, which makes
life easier on users. Perhaps combined with a QDox-like setup (for the
lazy but disciplined user) this is way better than the others. It is I
think also the most performant and it results in the cleanest code.

Finally, I think the first approach is also the quickest one for server
apps as all the work (parsing and verifying context criteria and making
sure they can be satisfied) occurs on startup, or even potentially right
after compilation.

Services don't go into the Context
----------------------------------
This is something very important to stress. Services should not go into
the context. If it is something a component acts upon actively and
contains some business logic for, it probably isn't a component and can
go into the context. If not, it is probably a service which you should
handle through ServiceManager.

example: a home directory where a component is allowed to interact with
the filesystem is something to put into the context, whereas a storage
service with which a component can interact to store data is not.

I tend to try to use context as little as possible (in the above
example, you'll see me always using the storage service rather than the
context entry). While usually easy in the short run and very useful for
quickly avalonizing existing apps, in the long run using context a lot
decrease application maintainability.

This is a lesson learned from working with Servlets (among other
things). Generally, using the request and response stuff in the servlet
context works really well (as you know it is there per the servlet
spec).
Using container-specific materials (ie stuff that WebLogic might put
into a servlet context but that ServletExec does not) is a road to
disaster as someone will find out when they have to move over that
application you wrote two years ago to the new server farm and the only
exception he gets is IllegalArgumentException :D.

The balance between all this is often difficult to find ;)

Properties and configuration don't go into the Context
------------------------------------------------------
If it is just as easy to use properties, parameters or configuration
objects instead of the context (ie if you have data naturally expressed
in character form), choose that option anytime. It's a lot clearer and
generally less error-prone.


okay, sorry for the length of the e-mail, but I don't want Steve to feel
too alone down at the bottom of lengthy talks :D

g'night all,

- Leo


--
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