| Draft for Magnolia 4.2+ Draft for Magnolia 4.2+ - Introducing IOC in Magnolia |
Rationale
Inversion of Control (or Dependency Injection) has been around for a while now, and is recognized and accept as a design principle which helps improving testability (and thus quality), code readability, maintenance, understandability, and countless other advantages.
MAGNOLIA-2569@jira
Mandatory read: Martin Fowler's article! Wikipedia's article is probably also a good read, altough maybe a little on the theoretical/abstract/wank side:
The two main injection mechanisms are "setter injection" and "constructor injection". The latter is superior in many aspects:
Implementation
One of the points that was preventing us from introducing IOC in Magnolia was our observed components, which could not be "updated" - the singletons are currently re-instantiated when observation kicks in. MAGNOLIA-2553@jira provides a patch for this.
Not all of Magnolia needs to be "converted" in one goal, but there's a good chance changes to one component will lead to changes in its dependencies.
Benefits
- Testability
- Clearer APIs
- Better dependency management (which component uses which others), less singletons
- Less brittle code (singletons = global state)
- Cleaner lifecycle management of components
Conversion
Some components will not be "convertible" as-in. In some instances, we will maybe need to introduce "provider" components. Some components might need to be renamed, and we will have to come up with naming conventions (what is a component, what is a "value object" - for instance, the module configuration beans are probably the latter, although we'll want to inject them)
Library or custom ?
Guice or picocontainer would be my choice. Pico has 3 advantages for me (greg): I have experience with it, I have direct contact with several of the core developers, and lastly, it seems more implementation-agnostic. (by default, you don't need to introduce anything from the Pico libraries in your components; I believe Guice also has this ability, but its default behaviour is to use annotations)
Ideally, this shouldn't introduce dependency on whatever library we're using in our code (other than, obviously, the code that initiates and manages the containers, etc)
Scopes
Picocontainer introduced the notion of scoped containers (Guice has this too). The default example is in a web application: some components's lifecycle are tied to the webapp's lifecycle, while other components have a session-lifetime, or a request-lifetime. This is usually implemented by nesting the containers, and storing them as webapp-, session- and request- attributes. Since they're (really) lightweight, there's virtually no added performance cost.
In the case of Magnolia, we might consider more custom tailored scopes (no specific idea yet, just throwing the thought out there)
First targets
- Components like TemplateManager, ParagraphManager and so on, are possibly good candidates for IOC-isation; otoh, they're used in so many places, that it might be hard to get a valid proof-of-concept out of this. (we will probably have to keep the singleton-getInstance access methods for a while...)
- ModuleManager, update mechanism, lifecycle etc might also be good candidates.
- Context.
- FactoryUtil.get* -> query container then fallback on legacy implementation
- Rendering, models, etc.
Possible issues / obstacles /
- where to start (possibly with only the app-scope - see potential issue with context...)
- naming conventions (will we rename classes if appropriate?, )
- "converting" little by little, and maintaining a bastard codebase, where some components would be handled both by the ioc container and regular FactoryUtil calls.
- scopes vs context (we have a lot of components which would be candidates for app-scope, except at some point or other, somewhere down their dependency hierarchy, they'll use, for instance, MgnlContext.getInstance. And worse, might fallback on the system context, or change their behaviour one way or the other, if no Context is set. These behaviours will somehow have to be wrapped and retrofitted in MgnlContext, see #3
More interesting read
- http://misko.hevery.com/code-reviewers-guide/
- http://picocontainer.org/ - not only pico's docs, but also a bunch of interesting articles about patterns and anti-patterns
- http://code.google.com/p/google-guice/
Comments (3)
Mar 26, 2009
Boris Kraft says:
Another option maybe http://tapestry.apache.org/tapestry5/tapestry-ioc/ and for...Another option maybe http://tapestry.apache.org/tapestry5/tapestry-ioc/
and for completeness' sake: Spring.
Mar 26, 2009
Grégory Joseph says:
Spring does everything and the coffee; pico and guice do one thing and do it wel...Spring does everything and the coffee; pico and guice do one thing and do it well. I suspect tapestry-ioc to be the same (i.e targeted at usage within tapestry applications); both might still be worth a look, especially if we want to stay container agnostic.
Jun 30, 2009
Philipp Bärfuss says:
I would not drop a basic custom solution to fast. Such a basic solution could si...I would not drop a basic custom solution to fast. Such a basic solution could simply:
This way would could introduce IOC with a small amount of changes.
As far the setter/constructor discussion goes: the setter approach is simply more near the way magnolia handles object creation in today (FactoryUtil or c2b).
To keep the injected objects up to date (in case an observation is triggered) the approach described in MAGNOLIA-2553@jira could be used.