Menu

Web Services Integration Patterns, Part 1

June 16, 2004

Massimiliano Bigatti

A few months ago I was working on a Java application for the banking industry. There were a bunch of hosted services that we had to integrate in our software. We had to use web services, since the SOAP protocol was the bank's standard way to access legacy data, throughout CICS transactions.

The bank's middleware department, in fact, has a cluster of WebSphere application servers running the integration layer which allows the applications to communicate with the mainframe using SOAP. This layer is a home-grown integration software using a variety of proprietary protocols to exchange data with COBOL transactions; the raw data is then converted to SOAP messages. This common) approach allows the applications to deal with CICS transactions as if they were web services.

On our side some work was already done: the first services were in place and working. But the first developers didn't bother with the clarity or the extensibility of the host integration layer. So we started reworking the code, creating a little integration framework and, not suprisingly, we started using Design Patterns.

The interesting thing was that I started thinking that some solutions could be considered to be "Domain Patterns", a specialized form of the original Gang of Four (and other) patterns, placed into the context of web services and integration. In this and in my next article you'll find a catalog of such patterns, plus some others that seem to be common sense and which I found useful organizing in a pattern template. These ideas came from our own case study; perhaps they will be useful in your work.

Here is a quick list of what follows in this first installment:

  1. Service Proxy
  2. Proxy with Channel
  3. Service Coordinator
  4. Service Simulator

The second installment will cover five more patterns.

The implementation of those patterns in our software allowed us to gain some advantages: first, we had a common language to describe the components of the systems and the interactions; second, our integration layer had a more polished aspect, that is, it was more readable. Third, following these structures we were able to reuse common base classes and build a simple framework.

Service Proxy

Each service is represented by a Service Proxy

When you need to integrate an external service, you can link directly to the service in your client code, using some specific API. For example, to create a Java client for a SOAP web service you can use the JAXM (Java API for XML Processing) APIs:


public float getRate( String currency1, String currency2 ) {

 MessageFactory mf = MessageFactory.newInstance();

 SOAPMessage msg = mf.createMessage();

 SOAPPart sp = msg.getSOAPPart();

 SOAPEnvelope env = sp.getEnvelope();

 SOAPHeader hdr = env.getHeader();

 SOAPBody bdy = env.getBody();



 String xsi = "http://www.w3.org/2001/XMLSchema-instance";

 env.addNamespaceDeclaration("xsi", xsi); 

 env.addNamespaceDeclaration("xsd", "http://www.w3.org/2001/XMLSchema");

 env.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");



javax.xml.soap.Name xsiTypeString = env.createName("type", "xsi", xsi);



 SOAPBodyElement gltp = bdy.addBodyElement(

  env.createName("getRate","ns1", "urn:xmethods-CurrencyExchange")

 );



 SOAPElement e1 = gltp.addChildElement(

  env.createName("country1")

 ).addTextNode( country1 );

 e1.addAttribute( xsiTypeString, "xsd:string" );

...

more JAXM code

...

 return rate;

}

This approach has some problems. First, you're creating a hard coupling between your program and the service. Second, it becames more difficult to reuse the same service in other parts of the application: in such case you have to reimplement this specific call. Sure, you can reuse the code at the method level, but it is still awkward.

Using a service proxy instead, you can decouple the service from the main program, and you're able to leverage this extra layer to clear the interface and to implement some useful features, such as logging and coordination.

The proxy implements an interface that separates in three steps the operation required for performing the call:

  1. parameter passing
  2. web service calling
  3. reading the results

The first step is implemented by a series of setXXX() methods, one for each parameter involved in the call. The Service Proxy contains a setter for each parameter requested by the service.

Then the service is called by the call() method. After the method returns, the client code can get the returned data calling the getter methods getXXX():

We chose to organize the parameters and return values as properties of the service object rather than parameters, and we return values of a single method because we encountered several complex services, with many parameters (around 300) and equally numbered return values.

You typically use the Service Proxy when you start realizing that your integration code is getting bigger and bigger, multiplicating boilerplate code and replicating calls to the same service in different program locations. As a result, the code is more organized, as you have one service for one class. You decouple the techical layer and API used to access the service from the client code thus reducing the cost of a possibile API changeover.

Also, the Service Proxy

  • can be grouped using packages, obtaining a hiearchy of services used by the application;
  • could subclass from an abstract class that could provide some additional services and boilerplate code;
  • could be coordinated by a third class organizing your services in an operation flow.

The Service Proxy class represents your single service, so you can use it in a UML diagram to show relationships, collaborations and more. It should not, however, become a kind of orchestraction, i.e. calling multiple services. This functionality is covered by the Service Coordinator pattern which builds on the Service Proxy pattern.

Sample code

The figure shown below illustrates a Service Proxy that represent the Currency Exchange Service:

The two parameters, country1 and country2, are implemented by the setCountry1() and setCountry2() methods:




Service service = new ExchangeService();

service.setCountry1( currency1 );

service.setCountry2( currency2 );



After setting the parameters, the client should call the service and read the response:




service.call();

System.out.println( service.getRate() );



Derived from

[gof]

Proxy with Channel

Each service decouples communication using a channel object

As an evolution of the Service Proxy pattern, it is possibile to decouple the protocol layer from the service aspect. This allows you to change the physical protocol used; for example, replacing SOAP with JMS or vice versa. Decoupling makes the single service and channel class simpler.

When you start implementing your own integration framework using Service Proxy classes, you'll end up with a common abstract class that hosts the boilerplate code used to costruct and decode the SOAP messages. This way, you keep the single Service Proxy classes simpler, but you hardcode in the class hierarchy the wire protocol you're using. Even using JAX-RPC technology you're tying your code to SOAP. It is, despite the API designer's intentions, the only protocol implemented. Even if future implementations will provide different protocols, JAX-RPC architecture requires that you have to regenerate the stub and skeletons for each protocol used.

Decoupling the service from the Channel means that you separate the service-specific computations (data formatting, mathematical operations, descriptions/codes lookup) from the wire protocol used.

This could be useful if the service you're calling will change the protocol, maybe passing from SOAP 1.1 to 1.2 or XML-RPC or REST. Having a Channel object that encapsulates the wire protocol makes the change more easy.

This approach comes at a cost. If your framework or application is lightweight and calls few services, and those services aren't expected to change in the short term, maybe the Proxy with Channel is overkill for you.

Participants and collaborations

The Service Proxy Object element represents the business service, which encapsulates the application domain logic. Every if and for here is business oriented. The Channel object provides the technical support for implementing the communication, providing the access to the specific wire protocol.

The Service Proxy object has a strict coupling with the Channel object, which conforms to a specific interface. When the call() method is invoked, the input data is passed to the channel object, along with information like authentication data or service URI. The Channel object then invokes the service, returning the response to the Service Proxy.

Sample code




public class ExchangeService extends AbstractService {

        String country1;

        String country2;

        float rate;

        

        Channel channel;

        

        public ExchangeService( String user, String password, Channel channel ) {

            this.channel = channel;

            

            this.channel.setUser( user );

            this.channel.setUser( password );

            

            this.channel.setServiceURI( "http://myrates.com/services/exchange/" );

        }

        

        public void call() throws ServiceException {

                try {

                        channel.setParameter("country1", country1);

                        channel.setParameter("country2", country2);

                        

                        channel.call();

                        

                        rate = channel.getResultAsFloat(

                                "\\SOAP:Envelope\SOAP:Body\getRateResponse\rate"

                        );

                } catch( ChannelException ex ) {

                        throw new ServiceException( ex );

                }

        }

        

        public void setCountry1( String country ) {

                country1 = country;

        }

        

        public void setCountry2( String country ) {

                country2 = country;

        }

        

        public float getRate() {

                return rate;

        }

}



Using XPath with SOAP

Service Coordinator

Interaction between the program and other services is mediated by a coordinator

Often business processes require the interaction of several services. Sometimes such coordination is performed by off-the-shelf web services orchestraction products; sometimes it is implemented by the application logic.

The coordinator class implements the second option, that is, it implements the logic required to manage the interaction between the services.

The services you need to integrate in the process could raise questions. For instance, they could require a distribuited transaction system or the output from one service is used as input for another service. In such cases implementing the logic in the client code ties the particular process required to perform a business function with the available services to the application. But the problem is that the process could change, maybe because the required services change themselves. As a result, the developer must rewrite the orchestration code inside the application.

A better approach is to have a third element that encloses only the logic needed to coordinate the required service. The logic is thus specific to the service used but responds to the business interface required by the application.

The client class requests the operation to the coordinator, which in turn calls the proxies as requested by the logic contained in the coordinator object.

All the logic related to the coordination of the service for a particular business action is encapsulated in a single class, making the program more clear. The client code is simpler, as it calls only the facade method of the Service Coordinator object, running the whole process.

Sample code

CountryLookupServiceExchangeService



public class CurrencyExchangeCoordinator {



    Service lookup;

    Service exchange;

    

    public CurrencyExchangeCoordinator() {

        lookup = new CountryLookupService();

        exchange = new ExchangeService();

    }

    

    public float exchange( String currencyFrom, String currencyTo, 

            float value ) throws ServiceException {

            

        lookup.setCurrency( currencyFrom );

        lookup.call();

        String country1 = lookup.getCountry();



        lookup.setCurrency( currencyTo );

        lookup.call();

        String country2 = lookup.getCountry();

        

        exchange.setCountry1( country1 );

        exchange.setCountry2( country2 );

        exchange.call();

        

        return exchange.getRate() * value;

    }

    

}



Derived from

[gof][sun]

Service Simulator

The service proxy simulates the original service providing the same interface without implementing any communication

Often the real service that should be called by an application is not available. Maybe you're developing in a disconnected environment, or the business process that you have to integrate is not available in the development environment you have to use. The Service Simulator uses a special channel that simulates the communication, so you can test the application without using the real service.

Of course, you can implement a test service that responds to the same interface used by the real service, and then point the application to the test service, but this approach may be too costly in some cases.

The Abstract Service class contains the base functionality for the Service Proxy and hosts a Channel. This component describes the interface of a generic Channel that is implemented in one concrete implementation for each protocol supported (SOAP, XML-RPC, and a REST implementation). In addition to these implementations there is a SimulationChannel, which is basically a no-op.

The client instantiates the Service Proxy, passing an instance of Simulation Channel to it. At each request, the Abstract Services will relay to that channel, which actually does nothing, returning dummy values in place of the response data.

The Simulation Channel can be used to create a test suite for the application.

Sample code

The following class shows an example implementation of the SimulationChannel class; most of the methods doesn't do anything, except for getValue(), which simply returns the XPath expression passed as the parameter:




public class SimulationChannel implements Channel {

    public void setParameter( String name, String value ) {

    }

    

    public void call() {

    }

    

    public String getValue( String expression ) {

        return expression;

    }

}



This way the client code gets back some test data; but this aspect could be improved, maybe returing the contents of a test data file:




public class SimulationChannel implements Channel {

    String xml;

    

    public SimulationChannel( String testFilename ) 

            throws FileNotFoundException, IOException {

            

        //load the xml test file in memory

        xml = readFile( testFilename );

    }

    

    public void setParameter( String name, String value ) {

    }

    

    public void call() {

    }

    

    public String getValue( String expression ) {

        return XPathEvaluator.evaluate( xml, expression );

    }

}



Derived from

[gof] Strategy, [fowler] Special Case

References

  • [gof] Gamma et.alt., "Design Patterns", Addison-Wesley 1995
  • [sun] SUN Microsystems, "J2EE Core Patterns", Prentice-Hall 2001
  • [fowler] Martin Fowler, "Patterns of Enterprise Application Architecture", Addison-Wesley 2003