Building Manageable Web Services from the Ground Up and the Management Criteria that Developers Must Consider

This tutorial written by Dan Foody originally appeared in XML Journal.

Life is full of compromises, and application development is no exception to the rule. So, when that project deadline is looming (and it always is) you are faced with three options:

  1. Finish the functional application
  2. Ensure that the application is manageable
  3. Ask your manager for more time so you can accomplish both

Since number #3 might be career limiting, it's usually a choice between #1 and #2. And no one gets extra points for manageability if the application doesn't function as advertised. Clearly, creating a fully functional application wins every time - When timeframes are tight, management functionality is often the first thing cut.

Good tools are available to automate the management of traditional applications built on application servers - you can find them in systems management suites like HP OpenView, Tivoli, and Unicenter. Great news if you are building a traditional monolithic application, but what if you're building an application using Web services? How do you build management capabilities into an application that is not stand-alone, but rather composed of a collection of services from multiple sources?

While Web services applications are more flexible, quicker to build, and quicker to adapt than their monolithic counterparts, they are inherently more complex because there are many more moving parts and myriad interdependencies. A problem with a single Web service has the potential to bring down the whole network of Web services. This complexity makes effective management even more critical for Web services applications than for traditional applications. Unfortunately, traditional management systems were never designed to manage applications composed of multiple separate yet interdependent parts.

Web Services Management

This article explores four management criteria that developers must consider when building applications based on Web services: security, monitoring, service location, and versioning. It also examines the specific challenges associated with each criterion.

Security

With traditional applications, managing access control typically means "check IDs at the front door." With Web services applications, there are two additional considerations:

  • Web pages are typically protected by URLs; each URL can be assigned different access rights. A complete Web ser- vice, however, is represented by a single URL. For example, a Customer Web service may have operations for querying, creating, deleting, and updating customer records, yet it would have only one URL for all four operations. As a result, it's difficult to control access to read-only query operation separately from the operations that change data.
  • The "front door" approach to security does not work with an application built from multiple services. You need to consider security at each service, and how the security settings for the individual services relate to the application as a whole. For example, let's assume the Customer Web service depends on two other Web services: Billing History and Order History. To allow each of the four Customer Web service operations (query, create, update, and delete), what security settings do we need on Billing History and Order History to make everything work? When the Customer Web service calls Order History, who should the Order History Web service assume is making the call: the Customer Web service itself, or the caller of the Customer Web service?

Monitoring

The challenges for monitoring mirror those of security. Most management systems and application platforms deal with monitoring at the URL level at best. This means that statistics for all Web service operations on a service are lumped together. Since many Web services include read-only operations (e.g., query) as well as transactional operations (e.g., create/update/delete) - and since the characteristics of read-only operations differ greatly from those of transactional operations - grouping them together generally results in gibberish. To properly manage a Web service, you must have operation-level granularity of statistics.

Beyond monitoring at the operation level, you may also want to further organize the statistics. For example, are you meeting service level agreements for Gold customers using the Customer Web service? Or, what is the average network latency of requests from the Customer Web service to the Order History Web service?

The second challenge for monitoring a Web service application is related to the fact that many distinct parts make up the whole. Knowing how these parts interact is important to knowing how the whole application will work. For example, if some of your customer record updates are failing, does the problem lie with the Customer Web service itself? Or Order History? Or Billing History?

Service location

Since a Web services application is not one piece of software deployed on one system (or on a cluster of identically configured systems), how do the different parts of the application know where the others are? In the example, how does the Customer Web service know where Billing History and Order History reside?

This problem is somewhat easier to solve if a single IT group has oversight of all the separate parts (the customer systems, the order history systems, and the billing history systems).

But once ownership for these systems doesn't all rest in the hands of one group, or you need to call Web services from systems outside your own firewall, keeping track of locations becomes a formidable challenge.

Versioning

In cases where different services are owned by different groups or consumed by multiple applications, managing service versioning warrants careful consideration. If the service is relocated to add capacity, how is this done without disrupting consumers? How is an upgrade for the billing system rolled out to the Customer Web service and other consumers? With monolithic systems, all parts of an application are versioned and rolled out at once, so problems of this nature don't arise. But they are critical considerations in the world of Web services.

Addressing Challenges

As with anything in software, all of these challenges can be addressed by simply adding code to your application, which will be explored in this section. However, it's important to weigh the consequences of hard coding. The byproducts of hard coding management functionality into service-based applications include inflexibility and brittleness - side effects that can undermine the benefits of a service-oriented architecture. There are alternatives that help minimize these consequences, which will also be discussed.

Security

Most application server platforms provide two methods of authorization: declarative and programmatic.

Declarative authorization allows you to separate the authorization from the code itself. It can be updated and viewed apart from the application code, meaning you don't need to recompile the application to update the security requirements. The following shows an example of declarative security in Java, which limits the access of the Customer Web service to customer service representatives:

<security-constraint>
<web-resource-collection>
<web-resource-name>CustomerSvc</web-resource-name>
<url-pattern>/services/CustomerSvc</url-pattern>
...
</web-resource-collection>
<auth-constraint>
<role-name>CustomerSvcRep</role-name>
</auth-constraint>
</security-constraint>

Declarative authorization is generally available only at the URL level (unless your Web service application is written using EJBs, which allow deployment-time per-operation authorization). If the ability to delete customer records must be limited to customer service managers, it requires programmatic authorization:

void DeleteCust(String custNo)
{
if(!Context.isUserInRole
("CustomerSvcMgr"))
{
throw new SecurityException(
"Please contact your manager");
}
...
}

Infusing the application with hand-coded security logic will result in brittleness, so it's best to use declarative security as much as possible (to define the minimum requirements for access to any operation) and only layer in programmatic security when absolutely necessary.

Beyond authorization at the Customer Web service, the next challenge is how to perform similar authorization at the Web services used by the Customer Web service. In many cases, these services need to know who the original caller is so they can properly perform their security checks. Doing this by hand is difficult and can open significant security holes. However, there are emerging solutions that leverage the SAML standard (security assertion markup language), which allows you to safely and securely transmit a caller's identity through a chain of Web services.

Monitoring

Beyond the statistics and information that can be gathered directly from your application server, tracking and monitoring per-operation or business-level statistics generally requires adding code into your application. For example, assume you want to track the number of customer records deleted each day, using the account closure rate as a measure of customer satisfaction. This requires the creation of a separate service dedicated to management. In this case, the service might have an operation called AccountClosureRate. The customer delete operation must be updated to compute the statistic:

void DeleteCust(String custNo)
{
if(!Context.isUserInRole
("CustomerSvcMgr"))
{
throw new SecurityException(
"Please contact your manager");
}
if(Statistics.isFirstDeleteToday())
Statistics.deleteCount = 0;
Statistics.deleteCount++;
...
}

Using this statistic the new management Web service would look like this:

long AccountClosureRate()
{
return Statistics.deleteCount;
}

You have the choice of making this new management service available as a standard Web service or making it a specialized management service built using a framework such as Java's JMX MBeans.

In either case, the primary downside (apart from all the additional coding) is the necessity of invasively modifying your application code to gather additional statistics about the service.

Service location

As discussed, the Customer Web service needs to locate the services it consumes. As the Customer Web service moves through the development-test-production cycle, the location of Order History and Billing History will change (for example, the test versions of these services will be in a different location than the production versions). Hard coding the URLs of these service providers into our Customer Web service won't work.

Most SOAP tools offer some help here. With Visual Studio .NET, for example, you can set the URL behavior property of a Web service to dynamic. This stores the endpoint of the service in a configuration file that can be changed without recompiling the application. Other SOAP tools follow a similar approach of storing the endpoint URLs in configuration files.

While this approach is a step in the right direction, the downside is that the location of each service provider is stored locally at each consumer. If Order History needs to be relocated, we would need to find and update the configuration files of all Customer Web service installations, along with those of other Order History consumers.

The solution to this problem is a directory service that enables consumers of services to look up the locations of their service providers in the directory - in this case the Customer Web service would look up both Order History and Billing History. Because the directory is shared by all the consumers, you need only change the one directory entry and all consumers see the change automatically.

While you could use any directory service, UDDI is purpose-built for Web services (in fact, a UDDI directory is accessed as a Web service). Each service instance is identified by a unique key (specifically, a binding template key). During development, you find the service you want to use and store the key. You then look up the service endpoint by key at runtime using the UDDI API.

BindingDetail detail;
Vector templates;
BindingTemplate template;
detail = uddi.get_bindingDetail
(orderHistoryKey);
templates = detail.getBinding
TemplateVector();
template = (BindingTemplate)
templates.get(0);
OrderHistoryURL = template.
getAccessPoint().getText();

The example above (written using UDDI4J) shows a simple lookup of the endpoint for the Order History Web service, assuming there is only one instance of the server (though UDDI allows multiples for simple load balancing and failover). In a production application you also need to be concerned with caching of UDDI entries. The general policy is to cache the UDDI entry for as long as desired, but if the endpoint is unreachable, reload the entry from UDDI (in the event it's been updated) and retry the request.

Versioning

The ability to change service endpoints with UDDI addresses some versioning issues as it allows you to bring a new version of the service online, update the UDDI entry to point to the new version, and then take the old version offline. If consumers follow the caching behavior described above, all consumers will automatically update themselves to point to the new version of the service.

However, versioning is not always so simple. The strategy outlined above has two limitations: (1) the new version's API must be completely upward-compatible with the older version and (2) all consumers must be cut over to the new service at the same time.

Adequately addressing versioning challenges requires a different architectural approach. For example, assume Billing History is being versioned. The most effective action is to insert an intermediary, i.e., a proxy, in front of the Billing History service. The intermediary can handle the logic necessary to adapt between the old and new APIs, gradually transferring the load to the new version of the service (perhaps one customer at a time, or prioritized by Gold customers vs. Silver customers). After the load transfer, the intermediary continues to adapt between the old and new versions of the Billing History API for all requests. Consumers of Billing History can upgrade to the new API at their leisure, after which they can bypass the intermediary. Once all consumers have upgraded to the new API, the intermediary can be removed.

While writing these types of intermediaries is possible, it is extremely tedious to do so by hand and without a careful design can lead to performance and reliability problems.

Factoring Out Management

Many of the techniques in the previous section require hard coding management behavior into the services themselves - thus locking in the management behavior until a new version of the service is rolled out.

Breaking this tight tie between service and management functionality is important if you need your IT environment to respond quickly to change. Management functionality must be factored out of the services themselves so that it doesn't impede change.

There are two ways to factor out management code. The first is to take advantage of the intermediary approach: build the management logic into a proxy that sits in front of the actual service. As consumers talk to the proxy, it performs its management tasks and then forwards requests on to the service provider. This approach clearly separates the management logic from the application, allowing you to update them independently. But, as discussed previously, poorly designed proxies can lead to significant performance and reliability problems.

The alternate approach is to build the management code as "protocol handlers" (SOAP extensions in .NET and JAX-RPC handlers in Java) that plug in to the application platform itself (not into the application code). This approach separates the code from the application and addresses some of the performance and reliability concerns. However, what it gains in performance and reliability it loses in flexibility. Management policy can't be updated without deploying new software on the application servers, reconfiguring the applications themselves, and restarting the applications.

Toward an Automated Solution

In the final evaluation, it's fair to say that hand coding management functionality is not only a hassle, but also ineffective in a Web services environment. The good news is that solutions are now available that automate a lot of what was discussed in this article, and much more.

This new solution category is called Web services management. Web services management solutions can be deployed either as proxies or agents (protocol handlers); the best solutions support both models as there are valuable use cases for both. Web services management solutions provide easy-to-use, flexible environments to build sophisticated dynamically versionable management rules and policy with just a few clicks of the mouse - avoiding all the tedious and time-consuming (not to mention brittle) hand coding you would otherwise have to do.

The ultimate success of a Web services application project includes the ability to effectively manage the Web services throughout their entire life cycle. Web services management solutions provide developers with the tools they need to simplify the process of building in manageability from the ground up.

About the Author:

As chief technology officer at Actional, Dan Foody leverages his extensive hands-on experience in enterprise systems integration software toward easing integration through Web services. He is an active participant in the Web services standards including WS-I and OASIS, where he spearheads Actional's contributions on the OASIS Management Protocol Technical Committee and its efforts to deliver XML-based Web services management standards. He is the author of various application integration standards, and contributed significantly to the OMG standard for COM/CORBA interworking.