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 2002/11/24 01:15:44 UTC

context usage patterns

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:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>


Re: context usage patterns

Posted by Stephen McConnell <mc...@apache.org>.

Leo Simons wrote:

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

:-)


-- 

Stephen J. McConnell

OSM SARL
digital products for a global economy
mailto:mcconnell@osm.net
http://www.osm.net




--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>


Re: context usage patterns

Posted by Leo Simons <le...@apache.org>.
On Sun, 2002-11-24 at 01:15, Leo Simons wrote:
> 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).

actually, this is a bad (because incomplete) example.

What should usually happen if you ask me:

1) you figure out your components need permanent filesystem-like data
storage
2) you use the StorageService component from the component repository
and add it to your assembly spec
3) StorageService gets a home directory from the container through the
mechanism described to handle the data storage (you could also provide
it with an URI in the Configuration)
4) your application components that need storage declare a dependency on
and get a reference to the StorageService

this is basically the Separation of Concerns pattern applied, separating
out "filesystem interaction" into its own entity.

note: things can become more complex if you are working with a very
dynamic runtime component setup (not in this example, but in the general
case).

cheers,

- Leo


--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>