JGATMS Architecture Overview

JGATMS is a web-based application built on the J2EE Platform. The client tier can be any web browser that supports standard HTML. As a J2EE web-application it adheres to the Model-View-Controller design pattern. Therefore, support for other clients can be added by introducing a new non-HTML View layer. Several open source Frameworks were used to implement the architecture. They include:

  • Struts. An implementation of the MVC design pattern produced by the Jakarta project of the Apache Software Foundation.
  • Commons-Chain. An implementation of the Chain of Responsibility design pattern also produced by the Jakarta project. Note: At the time of this writing commons-chain is still a "sandbox" component. This means that it is not really production-ready. No modifications were made to commons-chain to use it in this application.
  • Hibernate. An object-relational mapping tool supported by the JBoss Group.

The architecture of JGATMS is designed around a command pattern. This means that each function of the application is a command or series of commands executed in a chain. The set of commands available are stored in a catalog and accessible by name. The web-tier of the application is a set of Struts Action classes that execute one or more command chains. The application can be customized by adding commands to each chain and adding actions to execute new chains. Existing commands and actions can also be modified if needed.

JGATMS Context

The JgatmsContext class is an implementation of the context that provides easy access to JGATMS-specific attributes.

JGATMS Commands

As stated earlier, JGATMS commands are built on the still experimental Jakarta Commons-Chain component. Commons-Chain provides an API to build applications as very small pluggable units of funtionality. Components can be implemented as Commands or Filters . Commands are very simple Java classes that adhere to the following API:

public interface Command {
    public boolean execute(Context context) throws Exception;

Complex functions are built by configuring chains containing an arbitrary number of command implementations. JGATMS has implemented a base command class that contains several helper APIs. This class is inklings.jgatms.command.BaseCommand. Commands in the application should extend this class to take advantage of the helper methods and ensure that they live well within the application.

Commands have several semantics that must be understood to properly implement them:

  • Commands must encapsulate autonomous units of work. When writing a command be aware of whether you are doing too much. A command should do a single thing - typically a small thing. Even if a command has dependencies on another thing happening, those dependencies should be represented via the Context, not as inline code.
  • Commands must be written to live within a chain. Related to the last point, multiple commands will be chained together. Commands should be written such that they get dependent data from Context and set resulting data in Context.
  • Commands store data in the context object. Use "get" methods to keep track of the context keys for a command. This is a technique suggested in the commons-chain documentation. It clarifies what sorts of information the command will need access to. For example, if your command will need a Member value object, instead of calling context.get("member"), implement a getMemberKey() method on your command class and call context.get(getMemberKey()). Using this technique, people who look at the code for your command will be clear about what the command's dependencies are. Be sure that you don't rely directly on other commands, but use them indirectly via the Context. Simple value object sharing through the Context should be sufficient for most circumstances.
  • Command chains will execute until one of the commands returns true. To indicate that the chain should exit without completing, return true from a command. In nominal cases commands should return false. This is somewhat confusing at first since it feels more natural to return true in nominal cases.
  • Use the "dispatch" context attribute appropriately. JGATMS uses a "dispatch" key in Context to indicate the general outcome of a command chain. The ContextKeys class provides three keys that are generally useful: SUCCESS_DISPATCH, ERROR_DISPATCH, and FATAL_DISPATCH. The SUCCESS_DISPATCH is the nominal case and will be assumed if no other dispatch key is given. The ERROR_DISPATCH key is used to indicate that a correctible error has occured - such as a user input error. Using this dispatch will generally have the effect of returning the user to the previous screen to fix the error. The FATAL_DISPATCH key is used to indicate that a non-correctilbe error has occured, such as a database connection issue. Using this key will typically display an error page with instructions to contact a system administrator. In general, a command should not use one of these keys until an outcome has been determined. For example, you would probably only set the ERROR_DISPATCH or FATAL_DISPATCH if you are about to return false f rom a command because an error has been encountered. You should be very careful when assuming success by setting the SUCCESS_DISPATCH key because someone may reconfigure the command chain and place yours in a different place. You should also avoid relying on the dispatch key to be set a certain way before doing something. This would introduce subtle dependencies that might be quite difficult to debug. Remember, success will be automatically assumed if the chain completes and another command has not set a dispatch key. Ultimately, the value of the dispatch key will be used by the Action classes to determine where control should be dispatched after execution of the chain. So you can customize the application by setting any arbitrary dispatch key. It will automatically work as long as your key is configured as a forward definition in the struts-config.xml file. There are examples of setting the dispatch key in existing code, but in general you would do it like this: