Recipe10.5.Decoupling Your Application from External Services


Recipe 10.5. Decoupling Your Application from External Services

Problem

You want to decouple your Struts application from a specific implementation of an external business service.

Solution

Create an interface that provides an API for the business service. Then use a ServiceFactory class like the one shown in Example 10-13 to provide access to the service.

Example 10-13. Service factory
package com.oreilly.strutsckbk.ch10; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class ServiceFactory {     private static Log log = LogFactory.getLog(ServiceFactory.class);          public SecurityService createSecurityService( ) {         SecurityService service = null;         try {             service = (SecurityService)                 Class.forName(securityServiceImpl).newInstance( );         } catch (Exception e) {             log.error(                "Unable to create SecurityService for impl:"+                     securityServiceImpl);         }         return service;     }     protected void setSecurityServiceImpl(String securityServiceImpl) {         this.securityServiceImpl = securityServiceImpl;     }     private String securityServiceImpl; }

You can use a Struts PlugIn, like the one shown in Example 10-14, to bind a service implementation to the interface, and store a reference to the service in the servlet context.

Example 10-14. Service factory plug-in
package com.oreilly.strutsckbk.ch10; import javax.servlet.ServletException; import org.apache.struts.action.ActionServlet; import org.apache.struts.action.PlugIn; import org.apache.struts.config.ModuleConfig; public class ServiceFactoryPlugin implements PlugIn {     public void init(ActionServlet servlet, ModuleConfig config) throws                       ServletException {         ServiceFactory factory = new ServiceFactory( );         factory.setSecurityServiceImpl(securityService);         servlet.getServletContext( ).setAttribute("APP_SERVICE_FACTORY",                                                     factory);     }     public void destroy( ) {     }     public void setSecurityService(String securityService) {         this.securityService = securityService;     }     private String securityService; }

Discussion

In corporate environments, it's more likely that a web application will be used as a private intranet application than as a public site on the World Wide Web. These internal applications commonly interface to existing corporate software-based services. These services could be Java-based applications, Enterprise JavaBeans, or web services to name a few. Your web application will have little control of the interface to the legacy system; likewise, you're likely to receive little notice when the interface changes. You can mitigate these risks by decoupling your application from the underlying service using the approach shown in the Solution. Using the Solution, you can easily replace the interface to the implementation with a simplified implementation for testing and development.

Suppose your corporation provides a security service that authenticates existing users and allows the addition of new users. The current service is based on a legacy in-house developed application, but future plans are to move to a system based on Lightweight Directory Access Protocol (LDAP). Your Struts application that will interface to this system needs to be able to support the current and future implementations of the security service.

You can apply techniques in the Solution to this problem. The first thing you will want to do is define an interface for this service like the one shown in Example 10-15.

Example 10-15. Security service interface
package com.oreilly.strutsckbk.ch10; public interface SecurityService {          public void authenticate(String username,                               String password)          throws UnknownUserException, PasswordMatchException;     public void add(User user) throws DuplicateUsernameException;     }

For development and testing purposes, you can create an implementation of the SecurityService, like that shown in Example 10-16, that uses an in-memory Map of user data.

Example 10-16. In-memory security service implementation
package com.oreilly.strutsckbk.ch10; import java.util.HashMap; import java.util.Map; public class MemorySecurityService implements SecurityService {          public void authenticate(String username,                               String password)              throws UnknownUserException, PasswordMatchException {         if (users.get(username) == null) {              throw new UnknownUserException( );         } else if (!users.get(username).equals(password)) {             throw new PasswordMatchException( );         }         return;     }     public void add(User user) throws DuplicateUsernameException {         if (users.containsKey(user.getUsername( )))              throw new DuplicateUsernameException( );         users.put(user.getUsername( ),user.getPassword( ));     }          private static Map users;     static {         users = new HashMap( );         users.put("gpburdell","gotech");         users.put("fflintstone","yabbadabbado");         users.put("mpython","nopuftas");     }     }

Now you can use a ServiceFactory (Example 10-13) that knows how to create a new SecurityService given the name of a class that implements the SecurityService interface.

A Struts Plug-In (Example 10-14) can be used to create, configure, and store an instance of the ServiceFactory in the servlet context. The plug-in is configured in the struts-config.xml to create instances of the SecurityService interface using the specified implementation:

<plug-in className="com.oreilly.strutsckbk.ch10.ServiceFactoryPlugin">     <set-property property="securityService"              value="com.oreilly.strutsckbk.ch10.MemorySecurityService"/> </plug-in>

It's a good idea to create a BaseAction (Example 10-17) that accesses the servlet context to retrieve the service factory and returns an implementation of the service interface.

Example 10-17. Base action that provides a security service
package com.oreilly.strutsckbk.ch10; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.Action; public class BaseAction extends Action {          public SecurityService getSecurityService(HttpServletRequest request) {         ServletContext ctx = request.getSession(true).getServletContext( );         ServiceFactory factory = (ServiceFactory)             ctx.getAttribute("APP_SERVICE_FACTORY");         return factory.createSecurityService( );     } }

As you add more types of services, you can add corresponding methods to the ServiceFactory and ServiceFactoryPlugin. Alternatively, the ServiceFactory and ServiceFactoryPlugin could be made to return any type of service by some agreed on name. Regardless of your preferred approach, this Service Locator pattern yields code that is more flexible and easier to test than more tightly coupled systems.

See Also

Struts plug-ins are discussed in Recipe 2.1. The Spring Framework takes pluggability like this to the nth degree. Recipe 10.6 shows you how to integrate an existing Struts application with the Spring Framework.

The Service Locator pattern is considered a Core J2EE Pattern. A page that documents this and other related patterns can be found at http://www.corej2eepatterns.com/Patterns2ndEd/ServiceLocator.htm.

Martin Fowler compares the Service Locator pattern to the Inversion of Control/Dependency Injection pattern in his must-read article at http://www.martinfowler.com/articles/injection.html.



    Jakarta Struts Cookbook
    Jakarta Struts Cookbook
    ISBN: 059600771X
    EAN: 2147483647
    Year: 2005
    Pages: 200

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net