Adding Chaining Support to Spring Web MVC

Posted by ryan
at 11:52 AM on Tuesday, July 20, 2004



Having the ability to chain together independent and seperate modules of functionality as part of one line of execution is something I think is mandatory in an MVC framework, yet few support this powerful functionality (and even fewer support what I think is the proper definition of chaining). I started using Spring and the Spring Web MVC at work recently, and while I think Spring is great in how it allows, provides and forces proper design habits, its MVC framework isn’t quite as mature. First among my wishlist of unsupported features is chaining.

So why is chaining essential to any proper MVC framework? It is a failure of any framework to assume that one “action”, i.e. logging a user in or saving a piece of data, has only one linear line of execution. Each logical action is really composed of a series of decisions and calls, and each endpoint of an action can vary based on its context of caller. With Spring you can pull out each piece of logic into it’s own object and inject them into the one controller that handles that particular action. This is good in that it properly seperates each bit of logic, but it encompasses all the branching logic between the various pieces into one controller, which isn’t reusable at all. Chaining lets us keep the modularity of the above solution while letting each invocation dictate the branching logic.

First, let me explain what I think the ideal implementation of chaining is.

  • Each link of the chain must be independent from the others and completely pluggable into a chain at any point.
  • Each link of the chain doesn’t return the next link, but rather a result identifier that indicates (in business logic vocabulary) the result of it’s particular chunk of execution.
  • Each link of the chain can have its results interpreted differently depending on the context of its execution (i.e. depending on which chain it’s included in)
  • Each link of the chain has its own set of validation rules and dependencies.
  • Each chain should have its own session that allows each link to place domain objects and other indicators that may be used by future links.

So how do we make Spring support chaining? Since Spring takes control of execution from each controller using the ModelAndView object, we have to embed the chaining within the controllers themselves. While having a controller be a facade to chaining has its own set of disadvantages (discussed later), it at least gives us most of what we’re looking for. The basic idea is that a controller that has complex or branched logic calls a chain in its doSubmit or applicable execution method, gets the result of the chain (in Spring’s case a ModelAndView) and hands it back to Spring. The controller is still responsible for the handling binding and validation, but submit execution is the reponsibility of its chain.

Since the concept of a chain is one not specific to Spring, most of the chaining classes aren’t dependent on Spring. All results and endpoints are Objects and are only cast to ModelAndViews within Spring controllers.

If we look at what we need a chain to do, it’s really quite simple. Here is the Chain interface:

public interface Chain {
    public Object executeChain(ChainableSession session)
      throws Exception;
}

All we ask of a chain is to execute and return a result. The ChainableSession is a wrapper for the request and response and also holds variables for access only within the context of the chain’s execution. A chain is really just an entry and exit point for the series of links, or Chainables, that make up the chain. Each link performs an action and returns a result of that action which can be used to determine the next link to execute. Let’s look at the Chainable interface:

public interface Chainable {

    public ChainableResult execute(ChainableSession session)
      throws Exception;

    public Chainable getNext(ChainableSession session,
            ChainableResult thisResult);
}

A Chainable is responsible for executing an individual or logical action and returning a result which states whether or not to continue with the chain. If the chain should continue execution, the Chainable is asked for its next link. You’ll note that the execute method only takes a ChainableSession. At one point I had it also taking the previous Chainable that was executed, but that would violate the independence of each link. Each chainable should be pluggable into any chain without having a dependency on a previous chainable. Now the chainable session can contain items from previous executions, but no explicit dependence on a previous chainable should exist. It is the responsibility of the chain’s caller to instantiate a session for the chain. This should be used to put things like the command object in the case of a simple form controller, or other runtime objects provided by the various Spring controllers that are needed by the chainables.

The getNext method determines what Chainable should be executed next. As a Chainable should be thread safe, the only items used to determine the next link should be the session and the result of this execution. The result contains a result id object that can be used to encapsulate any compound identifier needed when determining the next chainable. Let’s look at the ChainableResult interface:

public interface ChainableResult {

    public Object getResult();

    public Object getResultId();

    public boolean continueChain();
}

The difference between the result and the result id is simple. The result id identifies the outcome of the chainable’s execution and should be used in determining the next link of the chain. The result object is the result of the execution that’s returned when the chain completes execution. In the context of Spring, this will often be the ModelAndView. If a chain does not return a result, then it will be assumed that the response has been handled internally.

One thing we haven’t looked at is the ChainableSession. As I mentioned earlier, this is merely a wrapper for the request and response and a way to store items within the context of a chain’s execution.

public interface ChainableSession {

    public static final String ATTR_COMMAND = "COMMAND";

    public HttpServletRequest getRequest();

    public HttpServletResponse getResponse();

    public void setSessionAttribute(Object attributeKey,
              Object attribute);

    public Object getSessionAttribute(Object attributeKey);
}

The only thing of any interest is the ATTR_COMMAND static variable which is used to identify the Spring command object within the session. I don’t really like this variable here because 1) it’s spring specific and 2) it’s not a very flexible way to define such keys. If you have any ideas, let me know, I just hadn’t had the time to look at this particular issue yet.

Ok, so we have the pieces to make a chain, how do they fit into the controller paradigm? I’ve include a few simple controller implementations for convenience, but they’re really quite simple. Here’s the ChainableSimpleFormController implementation:

public class ChainableSimpleFormController extends SimpleFormController {

    private Chain chain;

    protected ModelAndView onSubmit(HttpServletRequest request,
              HttpServletResponse response,
              Object command,
              BindException errors)
              throws Exception {

        ChainableSession session =
          new ChainableSessionImpl(request, response);        
        session.setSessionAttribute(ChainableSession.ATTR_COMMAND,
          command);        
        return (ModelAndView) chain.executeChain(session);
    }

    public void setChain(Chain chain) {
        this.chain = chain;
    }
}

So how would this look in the spring-servlet.xml bean configuration file? Well let’s set up a scenario. In my case, we have an application that performs a series of branching decisions based on a user’s role and their access to any number of sites.

  1. If the user doesn’t have access to any sites, then display an error.
  2. If the user has access to one site then evaluate their permissions
    1. If the user has the super role, then display the site selection interface
    2. If the user has the site-only role, then display site information interface
  3. If the user has access to more than one site then display the site selection interface
We can see that there are three branch points, site access, number of sites, and role. If we make a chainable for each, we can string them together as independent, reusable modules. And in several scenarios within this application I’m able to reuse these chainables as part of a different chain. Let’s look at how the first of the chainables are configured.

<bean id="siteAccessChainable" 
  class="com.demo.web.chainables.SiteAccessChainable">
  <property name="noSitesResultId">
    <value>noSites</value>
  </property>
  <property name="hasSitesResultId">
    <value>hasSites</value>
  </property>
  <property name="resultMappings">
    <map>
      <entry key="noSites">
        <ref bean="noSitesViewEndpoint" />
      </entry>
      <entry key="hasSites">
        <ref bean="userSitesChainable" />
      </entry>
    </map>
  </property>
</bean>

<bean id="noSitesViewEndpoint" 
  class="springx.web.webmvc.chainables.ViewEndpointChainable">
  <property name="view">
    <value>error.jsp</value>
  </property>
</bean>

<bean id="userSitesChainable" 
  class="com.demo.web.chainables.UserSitesChainable">
  <property name="oneSiteResultId">
    <value>oneSite</value>
  </property>
  <property name="manySitesResultId">
    <value>manySites</value>
  </property>
  <property name="resultMappings">
    <map>
      <entry key="oneSite">
        <ref bean="..." />
      </entry>
      <entry key="manySites">
        <ref bean="..." />
      </entry>
    </map>
  </property>
</bean>

These chainables are using an abstract chainable provided for convenience (AbstractResultIdBasedChainable) that lets us map a result id (in this case a string) to the next chainable to execute for that result. Since the results are seperated from the chainable bean, we can create a new bean if another chain has different branching logic. The noSitesViewEndpoint is how we can specify that the end of the chain has been reached and the given view should be displayed. We could just pass in the view to the chainable as a property, but that would limit the flexibility of the chainable since it assumes that that specific result will always want to go to a view.

Now all we have to do is define the chain, which is merely iterates over the sequence of chainables starting with the root chainable, and give that chain to the proper controller.

<bean id="siteAccessChain" 
  class="springx.web.webmvc.chain.impl.ChainImpl">
  <property name="root">
    <ref bean="siteAccessChainable" />
  </property>
</bean>

<bean id="startController" 
  class="springx.web.webmvc.controllers.ChainableController">
  <property name="chain">
    <ref bean="siteAccessChain" />
  </property>
</bean>

Hopefully that brings our little example to closure. I’ve tried to cram a lot of info about chaining into this post, and I hope it was with some clarity. The point of all this is to provide a way to seperate and modularize cleary independent branch points within the context of one action’s execution. There are several issues with the solution I’ve presented that I hope to address in the future. Here are some of them:

  • The ChainableSession is specifically tied to the HttpServletRequest and HttpServletResponse. Ideally a chain would be environment independent and could be used in dekstop swing apps etc… But seeing as how this is in the context of a Spring web-app, this is somewhat acceptable now.
  • There needs to be a cleaner, more extensible way to defined chainable session attribute keys.
  • It would be nice if the ChainableResult beans could be defined in the spring config files, right now I have them instantiated within each chainable
  • Each chainable should have it’s own binding and validation, instead of still having that done at the controller level. This way each chainable is a completely portable and self-contained unit.
Any suggestions on the above are welcomed.

The Goods
The springx.web.webmvc classes can be downloaded here. There’s not much documentation provided beyond what I’ve explained here, so feel free to pull it out and start playing.