Greetings citizens of Avalon!
My name is Jonathan Hawkes. I'm an Avalon enthusiast, an Avalon Developers' List
lurker, and a brand-spanking new IoC type 3 convert.
There has been a lot of discussion on IoC types recently, and I am grateful because it
has stimulated my thinking and hopefully sparked a couple of decent (though I dare not
pretend original) ideas.
I just finished reading Berin's note concerning IoC types and a super-duper container
(presumably Merlin) that would support as many types as possible. I also just combed
over some of the docs for PicoContainer; and like Avalon, some of the concepts tickled
my fancy. Immediately, I began to see advantages of each discipline and wondered if
the two schools couldn't be reconciled somewhat.
Consider the following...
public interface HelloService {
void printHelloWorld() throws IOException;
}
public class HelloServiceFrameworkImpl
implements HelloService,
LogEnabled,
Serviceable,
Configurable,
Initializable,
Disposable {
private Logger logger;
private ServiceManager sm;
private Configuration printerConfig;
private PrinterSelector printerSelector;
private Printer printer;
public void enableLogging(Logger logger) {
this.logger = logger;
logger.info("Logger acquired");
}
public void service(ServiceManager sm)
throws ServiceException {
this.sm = sm;
printerSelector = sm.lookup(PrinterSelector.ROLE);
logger.info("PrinterSelector acquired");
}
public void configure(Configuration config)
throws ConfigurationException {
printerConfig = config;
logger.info("Configuration acquired");
}
public void initialize()
throws Exception {
printer = (Printer) printerSelector.select(config);
logger.info("Printer selected");
}
public void printHelloWorld()
throws IOException {
printer.print("Hello world!");
}
public void dispose() {
printerSelector.release(printer);
sm.release(printerSelector);
}
}
public class HelloServiceIoC3Impl
implements HelloService {
Printer printer;
public HelloServiceImpl(Logger logger,
PrinterSelector printerSelector,
Configuration printerConfig) {
logger.info("Acquired Logger, PrinterSelector, Configuration");
printer = (Printer) printerSelector.select(printerConfig);
}
public void printHelloWorld()
throws IOException {
printer.print("Hello world!");
}
}
I hope that the example needs little explanation (it is a bit contrived, I admit).
HelloServiceFrameworkImpl would be the current Avalon framework implementation.
HelloServiceIoC3Impl would be a variant of an IoC type 3 implementation.
In the IoC type 3 variant, the constructor serves several functions.
1. It automatically publishes dependencies
2. It automatically resolves dependencies
3. It replaces LogEnabled, Serviceable, Configurable, and Initializable
Now, just a couple of days ago, I didn't think that this was such a good idea. For
one, with no ServiceManager, there is also no ServiceManager.release(). Also, what if
I want to resolve components dynamically?
What I am proposing is an Avalon/IoC type 3 fusion. Call it IoC type 3.14. :)
All Avalon framework containers must be able to assign a Logger, a ServiceManager, and
a Configuration to a component that implements LogEnabled, Serviceable, and
Configurable respectively. An IoC type 3 component in Avalon would be able to receive
these entities as well. But it could receive a Logger through its constructor instead
of the LogEnabled interface. And if a component so declared, it could receive a
ServiceManager through its constructor, allowing the component to dynamically resolve
dependencies. Configuration and Context objects could be resolved as well.
I call my proposal IoC type 3.14 because it places further restrictions on IoC type 3.
For one, the constructor MAY NOT declare any implementation classes. Each argument
in the constructor should satisfy a role and represent a component (or service)
interface. Multiple arguments fulfilling the same role are not allowed. Essentially,
this would make the two following implementations identical.
public class Impl1
implements Serviceable {
private Service1 service1;
private Service2 service2;
public void service(ServiceManager sm)
throws ServiceException {
service1 = (Service1) sm.lookup(Service1.ROLE);
service2 = (Service2) sm.lookup(Service2.ROLE);
}
}
public class Impl2 {
private Service1 service1;
private Service2 service2;
public Impl2(Service1 service1, Service2 service2) {
this.service1 = service1;
this.service2 = service2;
}
}
NOTE: This should also be acceptable
public class Impl3 {
private Service1 service1;
private Service2 service2;
private ServiceManager sm;
public Impl3(Service1 service1, Service2 service2,
ServiceManager sm) {
this.service1 = service1;
this.service2 = service2;
this.sm = sm;
}
}
NOTE: And this too
public class Impl4 {
private Service1 service1;
private Service2 service2;
public Impl4(ServiceManager sm)
throws ServiceException {
service1 = (Service1) sm.lookup(Service1.ROLE);
service2 = (Service2) sm.lookup(Service2.ROLE);
}
}
Containers must be able to resolve all dependencies in the constructor's argument set
(which must be a true set) or otherwise must deal with the failure as if an exception
was thrown from the Serviceable.service() method. Furthermore, the component
constructor should not have to deal with null references. Any null reference
propagated to the constructor is considered a container implementation error. The
order in which arguments (roles) are defined in the constructor is unimportant.
Strictly conforming components have a sole constructor.
A type 3.14 IoC component must not expect any concrete classes. This allows the
container to switch out service implementations at will.
Services obtained through the component constructor cannot be released in the usual
fashion. However, creative solutions are possible. A service implementation could be
forced to be thread-safe by having the component interface extend ThreadSafe... Or a
service that would be potentially applicable to pooling might define its own release()
method to release the component reference... Or for component X that is pooled, an
XPool service might be established. These are not proposed best-practices, just some
possible ideas.
Okay, to sum up, I propose a constriction of IoC 3 that will be termed IoC 3.14. The
constructor of an IoC type 3.14 component demands only interface parameter types (and
each one unique). It may throw an exception of any type.
Furthermore, I propose expanding the Avalon framework specification to support IoC
3.14. An Avalon compatible IoC 3.14 component can expect the container to always to
able to resolve a Logger, Context, ServiceManager, and Configuration. This proposal
suggests no alternative for lifecycle methods. All current Avalon framework
interfaces should continue to be supported in addition to the IoC 3.14 specification.
Existing framework interfaces, if used in conjunction with this proposal, should have
an overriding event.
If you've made it this far, congratulations! It's late and I'm writing and
brainstorming at the same time. Your comments, criticisms, suggestions are most
welcome.
Thanks!
Jonathan Hawkes