I'd like to share some thoughts on a future configuration architecture
within James.
= Roles involved in configuration process =
Trying to identify the 'players' in the game:
ConfigReader -
parses configuration file into a ConfigContainer
ConfigContainer:
holds more or less raw configuration data represented in a
hierarchical form.
ConfigMapper -
maps/injects the read data from the ConfigContainer into the
Configurables
Configurable -
all components receiving their configuration data from a ConfigContainer
Configurator -
a component doing changes to the configuration of a Configurable,
either by
(a) manipulating the ConfigContainer or by
(b) manipulating the Configurable directly
ConfigWriter -
serializing the current configuration to make run-time changes permanent
= James =
In James, these roles are represented like that:
ConfigReader: Phoenix/Avalon internal code
ConfigContainer: DefaultConfiguration, a generic, all-purpose implementation
ConfigMapper: Configurables do that themselves, their relevant
ConfigContainers are injected by the kernel
Configurable: all the service components implementing interface
org.apache.avalon.framework.configuration.Configurable
Configurator: (a) Phoenix/Avalon at startup/initialisation,
(b) JMX MBeans
ConfigWriter: n/a
= Postage =
Just to give another view to it, in Postage, it looks a little bit
different:
ConfigReader: commons-configuration
ConfigContainer: one concrete class per Configurable, more or less 1:1
ConfigMapper: mapping is done by PostageRunner and others
Configurable: beans on various levels, parameter injection per setter or
constructor
Configurator: the bootstrap class
ConfigWriter: n/a
= Some observations =
- Tight coupling between Configuration and Configurable -
In James, Configurables themselves read and even parse values from the
ConfigContainer once on startup (method configure()) and often there are
no setter methods (probably to make the components public interface more
lean). That makes it difficult to change those values dynamically at
runtime, e.g. through JMX.
The mandatory use of DefaultConfiguration here also makes the unit test
code much more verbose than neccessary.
- Dumb ConfigContainers -
Postage has 1:1 relationship between ConfigContainer and Configurable
classes. But its ConfigContainers don't inhabit any logic. They are
simple Data Transfer Objects. There is a mapper (class
ConfigurationLoader) to get the data from the ConfigReader into the
appropriate ConfigContainers and another mapper (class PostageRunner) to
get data out of ConfigContainers into Configurables. This is a bit of an
overkill and not a perfect solution either. But the decoupling of
Configurable and the rest of the system works pretty well.
= Propositions =
- Setters -
Let's add setters for all kinds of configuration parameters to the
Configurables in James. If a parameter cannot be set after a component
has become ready or live, the setter throws an
AlreadyConfiguredRuntimeException.
This would signal that the component is unable to cope with the change
and that the component would have to be restarted for the change to
become effective.
Let's not have the Configurables parse textual configuration parameters
into IPs, integers etc.
This could be started soon.
(BTW, this topic is not exactly JAMES-494, which deals with dependent
service injection. Here, I am talking about the internal fields a
Configurable populates by reading the configuration.)
- Restarting components -
Restarting (=redeploying) a component as the configuration was altered
is not an easy thing to do, IMHO. (Its easy if components are idle,
don't hold any resources and there are no dependencies between
components. That's not the canonical case.) It probably in the end
appears that the only reliable solution is to shut down the server and
reboot. So I would propose to support only dynamic changes the component
can handle while running and not support changes which would need
components to be restarted. The latter would have to be done offline then.
Maybe there is a solution to this problem in some container we come up
with. It's enough work to cope with simple configuration changes in the
first place.
- Configuration Mappers -
Let's introduce a bunch of Mapper objects, which are each able to take a
sub-Avalon-DefaultConfiguration, parse the Strings etc and inject the
specific data into the specific Configurable objects. When the avalon
configuration is eventually abandoned or deprecated, let's have another
bunch of Mappers for mapping from commons-configuration or whatever we
decide upon.
Let's not use those dumb ConfigContainers with no logic. The mappers
could also be the ConfigContainers. (I'd volunteer to refactor Postage
accordingly.)
Step by step we would move the access to DefaultConfiguration out of the
Configurables into specialized mapper classes.
- JMX -
MBeans would more or less directly execute operations by using the
Configurable's setters and methods like it is done at present. They
would not necessarily use the configuration framework.
JMX is not good at internally exposing component management. Geronimo
has explicitly moved away from being a MBean-based kernel, JBoss kind of
suffers from being one.
The reason for that is that the intended JMX granularity (the way a
remote managing person would like to have it) often does not match with
the server-internal granularity of Configurables/modules/components/classes.
JMX is good for being a remote 'high-level' API for management.
Let's go on with using JMX as we currently do, more or less.
(Sorry for the long post, any misuse of the english language and my
weird thoughts.)
Bernd
Stefano Bagnara wrote:
Steve Brewin wrote:
Steve Brewin wrote:
See attached component diagram. Obviously I can elaborate to
Class diagrams
if required.
All, please remember, this is just one way to solve the problem and is
posted for discussion.
If I understood the diagram you are for using simple java beans as
configuration object (or even interfaces like we already do with
protocol handlers or with the fetchmail configuration) and refactor our
components to use a setConfiguration(ComponentConfigurationBean conf)
instead of the avalon configure(Configuration conf).
Then you would use an Mbean implementation using CommonsConfiguration
(possibly mutable configurations) for the load/save operations.
This is clean and I like it, but maybe this works fine only for top
level components and not for generated components.
How would you handle the SMTP Command Handler configuration, for example?
Currently the SMTPHandlerChain read the Avalon's Configuration received
and look for "handler" sub-configuration. The handlers are Configurable
objects and they know how to load their own configuration.
If SMTPHandlerChain received a Bean and not a hierarchical Configuration
object this would not be possible if you don't want to read and evaluate
the handlers configuration when you instantiate the first top level
component.
Currently we have 11 classes (excluding tests) using the
"ContainerUtil.configure(" method: This means that in 11 instances we
directly delegate the configuration to a child object (like the example
I did for SMTPHandlerChain).
To apply the pattern you proposed we have to find out a solution for
each of this 11 specific use of the Configuration object.
2 on 11 are not a problem: POP3Server and SMTPServer. But we want to
remove the usage of a hierarchical Configuration object (being it Avalon
Configuration or the direct use of Commons' HiearchicalConfiguration) we
should refactor the other 9.
The analysis of the 9 solutions is too much for this discussion: I will
concentrate on the SMTPHandlerChain issue I spotted before. Have you any
proposal on that?
Stefano
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]