The problem
In our Spring application we found that while managers provide a nice simplified entry point into the functionality of the application (the Facade pattern), the underlying implementation was getting big & unwieldy as functionality was added over time. In our case, we also wanted to be able to customize the business rules & behaviour of the domain layer for individual customers.
What happened was that we would inherit from a core manager implementation class for a particular customer and modify the behaviour by overriding key methods per the Template Method pattern. So far so good but this generated two issues: 1) extension seemed like overkill if you just wanted to change a small piece of functionality and 2) our inheritance hierarchies were getting too deep as common extensions were refactored into common manager extension base classes.
The Solution
This seemed a good fit for the Chain of Responsibility pattern. The idea is that you take the request (a method call on the manager) and turn it into a command. Because it is a chain of commands, you can break up the monolithic logic into discrete commands where each command has a specific area of responsibility - in our case we had a command to do the actual work (e.g. insert a record in a database), another to create a "rich" audit entry in the audit trail[1], and another to generate a notification[2].
I think the Template Method pattern still applies, even when using commands. However, instead of deriving from a manager impl class, you derive from a core command class. The advantage is that derived classes are more closely aligned with the problem space.
The Implementation
Since I am a lazy developer, I decided to see if someone else had done this type of thing before by conducting an exhaustive search of available resources (ahem - I mean I searched Google). Suprisingly, there doesn't seem to be much out there. This could only mean two things: a) this is a novel approach to dealing with this problem or b) this is an insane approach to dealing with this problem. Optimistically I have assumed a) is actually the case, which is why I'm typing it up.
One of the frameworks that came up was Commons Chain. Given Craig McClanahan and Ted Husted were involved (I have much respect for these guys from the good old Struts days) I decided to give it a shot.
Here is what I did to integrate it into our application:
- Make commons-chain-1.0.jar available (to your IDE, ant, the web application, unit tests etc.)
- I created a CommandLoader class:
package com.acme.bigapp.domain;
and made sure it was configured as a Spring bean so it would get loaded:
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.chain.Catalog;
import org.apache.commons.chain.CatalogFactory;
import org.apache.commons.chain.config.ConfigParser;
import org.apache.commons.chain.config.ConfigRuleSet;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class CommandLoader implements InitializingBean, DisposableBean {
private ConfigParser parser;
public void afterPropertiesSet() throws Exception {
parser = new ConfigParser();
parser.setRuleSet(new ConfigRuleSet());
reload();
}
public void reload() throws Exception {
CatalogFactory.clear();
parser.parse(getConfigUrl());
}
public URL getConfigUrl() throws MalformedURLException {
return CommandLoader.class.getResource("chain-config.xml");
}
public void destroy() throws Exception {
CatalogFactory.clear();
}
public Catalog getCatalog(String name) {
return CatalogFactory.getInstance().getCatalog(name);
}
}<beans>
<bean id="commandLoader" class="com.acme.bigapp.domain.CommandLoader"/>
</beans> - An important detail in CommandLoader.getConfigUrl: it uses the class loader to load the command config file. This assumes the file is in the same package as CommandLoader. Here is some example context for the chain-config.xml file:
<catalogs>
<catalog name="Foo">
<chains>
<chain name="CreateFoo">
<command className="com.acme.bigapp.domain.foo.command.CreateFooCommand"/>
<command className="com.acme.bigapp.domain.foo.command.AuditCreateFooCommand"/>
</chain>
</chains>
</catalog>
</catalogs> - I then created a base class for manager impl classes to access commands:
package com.acme.bigapp.domain;
import org.apache.commons.chain.CatalogFactory;
import org.apache.commons.chain.Command;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Common manager methods.
*/
public abstract class AbstractManager implements ApplicationContextAware {
protected final Log logger = LogFactory.getLog(getClass());
protected ApplicationContext applicationContext;
/**
* Get a command. Commands are configured in the chain-config.xml file.
*
* @param catalogName name of the command catalog
* @param name name of the command or chain
* @return the named command, or null if the catalog or name are invalid
*/
protected Command getCommand(String catalogName, String name) {
return CatalogFactory.getInstance().getCatalog(catalogName).getCommand(name);
}
/**
* Factory method for creating new instances of a CommandContext that can be
* used in a command chain.
*
* @return a freshly minted instance of {@link CommandContext} with the ApplicationContext set.
*/
protected CommandContext createChainContext() {
return new CommandContext(getApplicationContext());
}
/**
* Utility method to execute a command. Catches checked execeptions and
* converts to a runtime execption to make exception handling a bit saner.
*
* @param command the command to execute
* @param context the command context
* @throws java.lang.RuntimeException wraps any checked exception.
*/
protected void execute(Command command, CommandContext context) {
if (context.getApplicationContext() == null) {
throw new IllegalArgumentException("The ApplicationContext is null");
}
if (command == null) {
throw new IllegalArgumentException("Command is null");
}
try {
command.execute(context);
} catch (RuntimeException r) {
throw r;
} catch (Exception e) {
throw new RuntimeException(command.getClass().getName() + " failed", e);
}
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(ApplicationContext context) {
this.applicationContext = context;
}
} - The method createChainContext() creates a context object to pass to commands. Since my commands will need to access the Spring ApplicationContext, I created an extension to the commons chain Context:
package com.acme.bigapp.domain;
import org.apache.commons.chain.impl.ContextBase;
import org.springframework.context.ApplicationContext;
public class CommandContext extends ContextBase {
private ApplicationContext context;
public CommandContext(ApplicationContext context) {
this.context = context;
}
public ApplicationContext getApplicationContext() {
return context;
}
} - In my manager impl class (derived from AbstractManager), I typically have code that looks like this:
public void createFoo(Foo foo) {
CommandContext context = createChainContext();
context.put("foo", foo);
execute(getCommand("Foo", "CreateFoo"), context);
} - A typical command would look like this:
package com.acme.bigapp.domain.foo.command;
import com.acme.bigapp.dao.foo.FooDAO;
import com.acme.bigapp.domain.CommandContext;
import com.acme.bigapp.domain.foo.Foo;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Encapsulate logic required to create a foo.
* <p>
* This is an example of the template pattern. It is intended for
* subclasses to overide templated method calls.
*/
public class CreateFooCommand implements Command {
protected transient final Log log = LogFactory.getLog(getClass());
/**
* Creates a new foo and puts it in the context variable "foo".
*/
public boolean execute(Context ctx) throws Exception {
CommandContext context = (CommandContext) ctx;
Foo foo = (Foo) context.get("foo");
preSave(context, foo);
save(context, foo);
postSave(context, foo);
context.put("foo", foo);
return false; // continue processing commands
}
protected void preSave(CommandContext context, Foo foo) {
}
protected void save(CommandContext context, Foo foo) {
getFooDAO(context).save(foo);
}
protected void postSave(CommandContext context, Foo foo) {
}
private FooDAO getFooDAO(CommandContext context) {
return (FooDAO) context.getApplicationContext().getBean("fooDAO");
}
}
Phew! That's it, thankfully.
Room for improvement
- One of the downsides to this approach is that you lose the ability to use dependency injection on your commands objects. This has a few consequences: 1) you have to make sure the commands have access to the Spring ApplicationContext, 2) you will have to create lots of silly helper methods that retrieve Spring beans in a typesafe manner and 3) you have to pass the context around a lot in template methods.
- I'm not too happy about how Commons Chain uses a Singleton to load the chains. I'd prefer more control over this and it feels too "hard" to me (i.e. not "Springy"). I'm speculating, but doesn't this get a bit hairy when multiple webapps are running in the same container all trying to access the single object?
- While we are nitpicking... I found that returning true or false from the execute method is confusing. Not that I have any better ideas, but this feels too tightly coupled to me - I guess I don't grok the use-case where a command gets to say if the rest of the commands in the chain should execute or not.
What's next?
- Given how simple the Chain of Responsibility pattern is, it seems tempting to write a more Spring friendly implementation...
- I'd love to explore the idea of using the Replicated Worker pattern as described in JavaSpaces Principals, Patterns and Practice.
Resources
- The commons chain "cookbook" covers the most common scenarios you will likely come across.
Notes
[1] We decided to implement handlers for each method when auditing rather than trying to encapsulate audit information via an interface as we took the position that business objects should not care about auditing particularly.
[2] The Observer pattern could quite possibly be used here as well.
Posted by: Wim | 2006.09.13 at 08:45 AM
Posted by: simbo1905 | 2008.10.23 at 03:07 AM
<bean name="myCommand1" class="MyCommand1"/> <bean name="myCommand2" class="MyCommand2"/> <bean name="batchSendChain" class="MyCommandChain"> <property name="commands"> <list> <ref bean="myCommand1"/> <ref bean="myCommand2"/> </list> </property> </bean>
Posted by: tom | 2008.12.21 at 05:40 PM