devxlogo

The COR Pattern Puts Your J2EE Development on the Fast Track

The COR Pattern Puts Your J2EE Development on the Fast Track

he Chain of Responsibility (COR) pattern is a behavioral pattern for decoupling requests from request handlers. It transparently routes requests to the appropriate handler and can (depending on the dispatching semantics) give multiple handlers an opportunity to handle the request. New handlers may be quickly and easily added to provide increased functionality.

For growing J2EE projects, this pattern can accelerate development by:

  • streamlining the introduction of new services
  • centralizing transaction or security concerns
  • minimizing plumbing overheads
  • reducing the number of Session Beans

The COR pattern provides a dynamic, loosely coupled interface for your client/server communication. It enables server components to be added transparently, without affecting your clients or requiring additional plumbing code. Alternatives such as session facades hardwire client/server interactions, and they provide structure and type-safety but at the expense of flexibility and maintenance. The client must always know which facade to call, so it is never totally decoupled: change a facade signature and the client has to change as well; add a session bean and the facade requires a new method. Your facades can quickly become bloated with too many fine-grained methods. Before long you have class files that are thousands of lines long, difficult to read, slow to load, and hard to maintain. Use the COR pattern to decouple your client/server components so that they can be evolved easily and independently.

With COR, you can fast-track your J2EE development so that your developers spend less time building infrastructure (Beans, facades, facade implementations, and service locators) and more time solving real business problems.

How the COR Pattern Works
The COR manager/dispatcher is responsible for managing a set of related services or request handlers (see Figure 1). Requests are sent to the COR manager and transparently dispatched to the appropriate underlying service. The request is passed to each service, giving the service a chance to process the request. One or more services process the request and return the response to the caller. The chain of services may be short or arbitrarily long.

Figure 1: The Chain of Responsibility Pattern

Using the COR Pattern in J2EE
By combining COR with another popular pattern, the Command pattern, you can facilitate efficient, extensible component coordination (see Figure 2). Subsystems do not reference each other directly. Rather, they request services via the COR Manager using commands. The COR Manager manages a set of services, routing commands to those services, managing transactions and remote boundaries, and returning replies. Requests may be modeled as Command data-transfer objects. Think of a command as a request for a service to perform a function or return data. A Service is a business-focused component (Java class) capable of processing one or more commands. In this approach, the COR Manager passes the command along the chain of services until the request elicits a reply. The service returns the results of the requested operation inside the command request and the command is passed back to the caller.

Figure 2: Using COR in J2EE

The COR Manager/Dispatcher transparently takes care of local or remote command execution and provides transaction demarcation. This coarse-grained control means that you don’t have to worry about transactions or remote and locale interfaces initially. Business logic can be deployed as COR services and Plain Old Java Objects (POJOS), since the COR Manager handles your transaction rollbacks and commits for you.

Transactions on Demand
Your command can specify at run-time whether it will be executed locally in the requester’s JVM or remotely inside the J2EE server. Some business operations (such as DBMS queries) do not necessarily require transactions. Such functions may be performed safely in the servlet tier. For commands requiring transactions (e.g., commands that involve database updates), the COR Manager routes the command via a Session Bean. The COR Session Bean is deployed using TX_REQUIRED. This guarantees that the command will be executed atomically; that is in one transaction.

Put COR into Practice
Step 1. Define Your Command Structure
Lay down a common marker interface for all your commands. Ensure your interface extends the Java interface Serializable so that it can be passed over remote boundaries. Your command interface forms the basis for your command hierarchy or structure. It should provide:

  • A mechanism for setting request parameters and getting request results
  • A method to specify whether the command is transactional
  • Any additional methods to assist the COR implementation

Create your Command base interface:

public interface Command extends Serializable{        /**     * Get the command params(s)     */    public Object getArg();        /**     * Get the results of the command being executed     */    public Object getResult();          /**     * is true if this command should be executed transactionally in the App server     * false otherwise     */    public boolean isTransactional();           public String getServiceManagerName();    public void setServiceManagerName(String name);}

All commands wishing to make use of the COR component must implement the Command interface. You may define as many command classes as you deem appropriate for your application. Think of your commands as the external interface into your business services, analogous to Session Facades.

Fine-grained vs. Coarse-grained Commands
Be careful with your command granularity. Make them too fine grained and you will have an almost one-to-one correspondence between a command and a method, which will quickly become unsustainable. Being overrun by commands is no different from bloated facades?it’s hard to maintain.

Generalize and parameterize your commands. Treat them as common data transfer objects. Define new command classes sparingly to minimize maintenance overheads. Encapsulate the specifics of your request inside the command.

Conversely, if you make your commands too coarse-grained, then they become unwieldy?hard to follow and hard to use. . If you make them too broad, you’ll have to spend time packing and unpacking command data. Balance your commands by structuring per horizontal service or by subsystem business component.

Step 2. Define Your COR Manager/Service Contract
The COR Manager/Service contract is the general contract between your COR manager and its constituent-chained services. At a basic level, your interface should define a way of communicating requests and capturing responses. Define a Service interface that takes a Command and returns a Command response:

public interface Service {    public Command process(Command command) throws  ServiceException;}

Service classes must implement this interface in order to participate in the COR chain.

Step 3. Write Your COR Manager Dispatching Logic
Create your COR Manager class and implement the Service interface. Place your dispatching logic inside the process method:

public class CORManager implements Service {protected List services;.../** * Routes to first service that responds to command. */public Command routeCommand(Command command) throws ServiceException {	for (int i = 0; i if (result != null) {			return result;		}	} // rof	throw new ServiceException("No Service found for command " + command);}

Your dispatching logic is responsible for passing the command to each service in turn until it gets a response.

Since you can pass a request to many services, the larger the number of services, the longer the potential turnaround time on a request. COR in itself does not provide constant-time response. For efficient routing, arrange your services in a significant order. Put your most frequently used services higher in the chain than your less frequently used services, so as to minimize routing overheads. Be careful with services that share commands. Depending on the order of the service in the chain and your dispatching logic, the first service may consume a command and prevent a second service from handling it.

Step 4. Plug in Your COR Controller Session Bean
With the basic interfaces and routing logic in place, you need to add a Session Bean to manage transactions. The Bean is responsible for accepting a Command, initiating a transaction, passing the command to the COR Manager again to route inside the server, and finally handling transaction commits and rollbacks. Ensure that your bean is deployed as TX_REQUIRED:

public class  CORControllerBean implements javax.ejb.SessionBean {...       public Command process(Command command) throws ServiceException{        try {            return ServiceManager.get(command.getServiceManagerName()).routeCommand(command);        }catch (ServiceException se) {            // Rollback transaction as an application checked exception has been thrown!            getSessionContext().setRollbackOnly();            throw se;        }      }}

Extend the COR Manager so that local commands are dispatched directly to the service chain and commands marked as transactional are passed through to the Session Bean:

public class CORManager implements Service {.../** * Route a command either to a SessionBean or to a Service class using the  * chain of responsibility pattern. */public Command process(Command command) throws ServiceException {		try {			if (command.isTransactional()) {			// mark with the COR manager name so that the bean can get the 
right COR manager instance to pass too
command.setServiceManagerName(name); return ServiceLocator.createCommandController().process( command); } else { return routeCommand(command); } } catch (ServiceException se) { throw se; } catch (Exception e) { throw new ServiceException(e); }}

With the COR Manager, Session Bean, and interfaces in place, you have a working COR framework. All that remains is to write your business functions as COR service implementors and configure them in your COR Manager instances.

Step 5. Write Your Business Logic Service Implementors
Service class implementors are simple POJOS that either perform business logic directly or act as proxies for other Java classes or Local/Remote Session Beans. Either way, your service implementation is responsible for deciding which commands it handles and which it does not. If you’re delegating to a Remote Bean, be sure to check whether the command can be processed prior to forwarding it. This preempts any unnecessary remote calls. Ignore any unknown commands by returning null to the COR Manager.

Create two services:

  1. A simple Echo Service responsible for echoing commands back
  2. A View Service responsible for satisfying ViewCommand query requests

Service 1. An Echo Service:

public class EchoService implements Service {	/**	 * Constructor for EchoService.	 */	public EchoService() {	}		public Command process(Command command) throws ServiceException {		return command;	}}

Service 2. A View Service:

public class ViewService implements Service {	/**	 * Constructor for ViewService.	 */	public ViewService() {		super ();	}	/**	 * Process the command. If it is a ViewCommand query the DBMS	 * and return the results inside the command.	 */	public Command process(Command command) throws ServiceException {		if (!command.getClass().equals(ViewCommand.class)) {			return null;		}		// process view command		ViewCommand vcmd = (ViewCommand) command;		try {			// Get your SQL  DAO    			SQLDAO dao = SQLDAO.get();						// Issue Query			List result = dao.executeQuery(vcmd.getDescriptor());						// set results back into the command sb			vcmd.setResult(result);		} catch (DAOException de) {			throw new ServiceException(de);		}		return vcmd;	}}

The COR Manager will create your Service implementors when it starts up. Ensure that your classes can be dynamically class loaded by providing an appropriate default constructor to perform any service initialization work.

Step 6. Configure Your COR Manager Instances
The final step to setup the COR Manager is to configure a COR Manager instance with the Echo and View Services. Drive this from a configuration file. Set up a common COR manager instance and add the services to the manager’s service chain. Put the View Service first in the chain and the Echo Service last to ensure that commands will be passed through the view service. This gives the view service an opportunity to process before it finally ends up at the Echo Service, which applies the default behavior by echoing the command back to the caller:

# The Default COR ManagerCORManager.default=common# The Common COR Mgrcommon.services=ViewService,DefaultService# Service Class ProvidersViewService.classname=au.com.ldabreo.services.view.ViewServiceDefaultService.classname=au.com.ldabreo.services.chain.EchoService

Adding more services is simply a matter of writing the service class and adding it to a service chain in the configuration file for your COR Manager instance. No change is required at the client level, as the service automatically gets included as part of the COR routing behavior.

Balancing Your Service Chains
Group services that are alike under the same COR Manager instance. Structure your groups so that you minimize your service chains. This becomes a trade-off between performance and flexibility. The most flexible approach is to centralize your services under one COR manager. This gives every service a chance to process the request. The least flexible but most efficient approach is to divvy up your services logically among your COR Manager instances.

Step 7. Execute Your Command!
To use the COR framework, create a command instance and pass it to the COR Manager to process. In this case, exercise the ViewService by using the ViewCommand. Set the isTransactional() flag to false since the query does not require a transaction and may be executed locally:

ViewCommand command = new ViewCommand(ViewDefnFactory.get().createViewDefn(
"au.com.ldabreo.services.examples.views","myview"));command.setIsTransactional(false);// Send to CORcommand = (ViewCommand) CORManager.get().process(command);// Process resultsObject[] rows = command.getView();for (int i = 0; i "Row(" + i + ") " + rows[i]);}

If you’ve set up your COR correctly, the ViewService will handle the ViewCommand and pass back the query results.

COR Trade-offs
The COR pattern illustrated in this article gives clear benefits in terms of clean separation between client/server tiers, centralized run-time transaction control, good encapsulation of server functions, and inherent extensibility. It enables business logic to be handled independently of the request, and it minimizes the impact of change. New components may be added easily without having to modify or add new client/server interfacing code. Components are free to collaborate outside packaging dependency restrictions. With transaction control in place, business functions may be quickly leveraged without having to write Session Beans. This means fewer beans (with their associated performance and deployment overheads) and faster development turnaround times. You can always add Locale and Remote components later, so as to support your target deployment architecture.

However, all patterns have their downsides. The COR pattern does not give constant time response, nor is it as type-safe as a Session Facade. Finer-grained deployment/transaction control behavior must be added into your business components as required. That said, the COR pattern, used wisely, can be a powerful enabling framework for facilitating fast growth, ease of development/deployment, as well as for reining in your J2EE bean/facade bloat.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist