Web Services Integration Patterns, Part 2
by Massimiliano Bigatti
June 30, 2004
In the first installment of this
article, I introduced some design patterns applied to the problem
area of web services integration. This article presents
more patterns:
- Configuration Driven
Service
- Data Logger
- Flow Logger
- State Logger
- Input/Output Validator
Configuration Driven Service
Input to the service isn't hardcoded, but driven by external
configuration data

Sometimes you can't hardcode the parameters used to call a
service because they can quickly change based on business rules.
The input could then be dynamically constructed, using a
configuration table. These parameters could be stored in a database
schema, an XML file, a properties file, or in other storage.
With the simple services you encounter in the tutorials, demos, or
web services technologies documentation, the interface appears
immediately clear, is usually made up of few methods, and embeds a
fair simple logic. However, in the real world web services may be very complex.
For example, we had to integrate a service for customer registry
census, which required many input parameters, as it needs
all the personal information about the customer, like name and
surname, addresses, social security number, and other data
collected by the application. We also integrated external data
banks, which provide financial information about the customers;
again, we had to pass a few hundred parameters.
Consider, too, that most web services are stateless: if
they relate to some specific data, each time they require even more
parameters to identify the primary key of the data being
processed.
In such cases we found it useful to externalize the name of the
parameters to pass and the logic used to obtain the values of that
parameters.
Take for example an e-commerce application that should store the
information about the order; maybe the customer is new, so
the user had to introduce some personal information. In the case
that the store operation is done by a web service, the
configuration for that service could be present in a database table
like this:
| SOAP parameter |
EL like expression |
Name
session.currentOrder.requestingCustomer.name
Surname
session.currentOrder.requestingCustomer.surname
Street
session.currentOrder.requestingCustomer.address.street
City
session.currentOrder.requestingCustomer.address.city
Country
session.currentOrder.requestingCustomer.address.country
ssn
session.currentOrder.requestingCustomer.ssn
The first column is the name of the SOAP element (the Service
Parameter); the second one contains an EL-like expression that
navigates the domain tree until it finds the getter that returns the
required value.
The service proxy relies then on the Configuration Manager to
load this table and to evaluate each EL expression against the
domain entities to obtain the actual values of such data.
The Dynamic Service Proxy represents the service, but its
interface doesn't contain all the setters that would be required to
replicate the service interface as in the Service Proxy pattern.
Instead, the Dynamic Service Proxy has a generic interface. The
Configuration Manager manages the configuration data, maybe
delegating data access to a DAO. The Configuration stores all the
information needed to perform the parameter-value mapping.
When the client requires a call() to the Dynamic
Service Proxy, it calls the Service Manager, requesting the loading
of the configuration for such service; this data, often
cached in memory, is used by the Manager to create the parameter
and values lists and passes them to the proxy.
Looking at the Dynamic Service Proxy, you don't see clearly the
service interface, so it
gives no immediate feedback of the business rules that drive that
particular service. The client code is decoupled from the service
proxy, allowing the two to evolve in an independent way.
Sample code
The CustomerStoreService is used to store the customer
data in the Customer Management Software; it is a subclass of
AbstractDynamicService, a common base class for
dynamic services. The implementation of the call()
method should invoke the Configuration Manager to load the
configuration of the specific services; the Configuration Manager
in turns obtains from the domain entities the data required by the
configuration and passes it to the service, calling the generic
setParameter() method:
public class CustomerStoreService extends AbstractDynamicService {
public void call() {
ConfigurationManager manager =
new ConfigurationManager( "CustomerStoreService" );
//add parameters and values
manager.prepare( this, session );
//calls the channel
super.call();
}
}
public class ConfigurationManager {
String serviceName;
Map cache;
public ConfigurationManager( String serviceName ) {
this.serviceName = serviceName;
initDAO();
}
public void prepare( DynamicService service, ApplicationSession session ) {
//loads the service configuration
ConfigurationData conf = loadConfiguration();
//obtain the values of the parameters, based on data session
Map values = loadValues( session );
//values the service parameters
prepareService( service, conf, values );
}
//load cache-enabled configuration data
ConfigurationData loadConfiguration() {
...
}
//obtains data from the domain entities
Map loadValues( ApplicationSession session ) {
...
}
//sets the parameter calling the setParameter() method on the service
prepareService( DynamicService, ConfigurationData conf, Map values ) {
...
}
}