Chapter 15. Designing Web Services

You can design a Web service from the viewpoint of the client or from the viewpoint of the service. Both are valid in terms of the application and the server architecture. You can certainly design for either one, and in this chapter, we'll examine the points that apply to each.

On the other hand, I think it's a mistake to take a single requirement, such as server-side performance, and drive all of the architectural decisions from that. I think it is usually best to take a more holistic view. Try to look at all of the major requirements, not just yours, but also those of the person with whom you will be interacting—the developer on the other end of the wire.

Design decisions almost always affect all areas of development. Of course each development area is overlapping and interdependent, but I find it helps tremendously to pull out common techniques that you can do in each area.

Performance

Right after interoperability, the questions I hear the most about designing Web services deal with performance. This isn't surprising, because XML is textual and obviously much larger than an equivalent binary layout would be. In fact, laboratory performance numbers comparing DCOM (Distributed Component Object Model) with ASP.NET Web Services back this up, as DCOM is almost ten times as fast when the logic being run is pure echoing of data, such as an echoString function.

Nevertheless, don't interpret lab tests showing binary formats as faster as proof that you should use a binary format. Often, the performance gains are almost entirely lost once real-world conditions are factored in, such as marshaling, network latency, and the actual amount of processing time a single operation takes.

That said, it would be a mistake to ignore performance completely. It's important to understand the performance considerations of your Web service. Coding standards and decisions you make up front can help to maximize the performance of the Web service, or Web service client.

Writing Performance-Oriented Code with .NET

One overriding recommendation I would make for writing code that performs well is to use the asynchronous pattern for all I/O operations, such as file, disk, and network access (including Web service calls).

This pattern isn't at all difficult to use: You just need to know how to create delegates for callbacks. For example, imagine that you are calling a Web service operation called SubmitPO. When you use wsdl.exe to create a proxy class, three methods will be created:

public POReceipt SubmitPO(PO po); 
public System.IAsyncResult BeginSubmitPO(
               PO po,
               System.AsyncCallback callback,
               object asyncState);
public POReceipt EndSubmitPO(
     System.IAsyncResult asyncResult);

The first thing to do is to write a function of your own that will be called when the operation is finished and has returned:

public static void MyCallback(IAsyncResult ar) 
{
     POClass class = (POClass) ar.AsyncState;
     POReceipt receipt = (POReceipt) class.EndSubmitPO(ar);
     //do something with the receipt
}

Next, you need to create an AsyncCallback that points to the callback function, and then call the BeginSubmitPO operation:

POClass client = new POClass(); 
AsyncCallback cb = new AsyncCallback(SomeClass.MyCallback);
IAsyncResult ar = client.BeginSubmitPO(po, cb, client);

At this point, when the Web service call returns, the callback will be called, and the return value can be accessed. By doing this, you can continue to work on the calling thread. This means that your user interface doesn't need to become unresponsive; and even in nonvisual applications, you can call multiple services nearly simultaneously. Instead of ten serial calls of 1 second each, for an elapsed time of 10 seconds, you can call the ten services simultaneously and get it over in 1.1 seconds. Of course, your results may vary based on hardware and other factors.

Message Size and Network Latency

One of the fallacies that many developers fall into is trying too hard to optimize the size of a message. For example, a cursory flick of logic may make one think that if a Web service is running at 400 requests per second, then cutting the message size in half would make it run at 800 requests per second. On second thought, it's apparent that this is not necessarily true. In fact, the extra processing power needed to make the message smaller may actually slow down the service.

Keeping an entire message under the MTU (Maximum Transmission Unit) size of a TCP/IP packet may result in a slight performance gain. In general, however, the thing to optimize is network latency. For example, even if you take your code's processing time from 0.2 seconds to 0.1 seconds, if the network latency is still 4 seconds, then the improvement doesn't make a big difference!

Optimizing latency on the network, especially when the Internet is involved, is challenging—you have to examine the actual physical topology of the network involved. Figure 15.1 shows the optimal network topology between a Web service and its client. It is an overly simplistic network, but the fact remains that from a latency standpoint, it is optimal. The typical Internet-based topology looks more like Figure 15.2, encompassing a large number of machines, such as bridges, routers, and hubs. The more you can get away from the topology in Figure 15.2 and the closer you can get to the topology in Figure 15.1, the faster your Web service will respond to its clients.

A Development Network …

Figure 15.1. A Development Network …

… and the Real Internet

Figure 15.2. … and the Real Internet

How Important Is Performance?

In the end, I highly recommend not worrying excessively about performance. It's important, and you should think about it up front. But the Internet and the Web are not conducive to high performance. No matter how much you do, you may never achieve your performance objectives. Even if you do, the cost for each gain in speed will take a logarithmic amount of work. Are you willing to spend a week to get a 1-percent gain? Do what you can, but don't expect miracles.

Interoperability

One of the key reasons to use Web service technologies in a distributed application is interoperability. Just as you can use HTTP and HTML browsers on nearly every operating system and platform on the planet, so too can you use SOAP and WSDL. These technologies enable you build a distributed application and deploy it in a heterogeneous environment of UNIX, Macintosh, mainframe, and Windows platforms. As well, developers of each service and client can use Java, .NET, Perl, and even Smalltalk.

This promise of interoperability isn't as clean as it could be. Just as a Web page will look different in Netscape and Internet Explorer, so too will SOAP messages be sent and read differently by different Web service stacks. However, builders of these stacks have done a lot of work to facilitate interoperability. Not every issue and bug have been fixed, but many scenarios work, and common customers are deploying real SOAP-based applications in heterogeneous environments.

This section deals with some of the design steps you can take early in your project to ensure maximum interoperability. However, the industry is moving very quickly, so consider this section to be in even greater flux than many others. That said, I've tried to focus on common interoperability principles that will apply for many years to come.

Audience-Centered Design

The first thing to realize is that your intended audience should dictate many of the interoperability steps you take. If you are building a service for use by a single partner on a J2EE platform running Linux, then factor that data into your decision up front. If you're exposing a service that an unknown number of people will use on an unknown number of platforms, then your decision will be different.

To begin with, educate yourself about the actual interoperability results that exist between any two platforms. The best way to do that is to review the interoperability results matrix found at http://www.xmethods.net/ilab or http://www.whitemesa.net. Like all URLs, these may not exist by the time you read this (although they've been very stable for the past 2 years). In that case, check my Web site at www.keithba.com/book for the new URLs.

If you are interested in the interoperability results for .NET or other Microsoft SOAP stacks, then also try http://www.mssoapinterop.org. This site contains the actual results for the SOAP Toolkit, ASP.NET Web Services, and .NET Remoting.

Once you know what stack your partner is using, the next step is to review these interoperability results and find a platform with good results that you feel comfortable developing on. For example, if your partner or partners are using ASP.NET Web Services and you are experienced J2EE developers, then Apache Axis or The Mind Electric's GLUE would be a great choice, offering a large amount of interoperability. Alternatively, if your partner is using Systinet's WASP for C++ and you are a Perl shop, then SOAP::lite may be your best choice.

Once you've decided on a platform, you'll need to design your service to maintain high levels of interoperability. For example, if you are using ASP.NET Web Services and you expose a DataSet object in one of your SOAP operations, then you may find that your partners have a difficult time interoperating. On the other hand, if you are expecting to interoperate only with other .NET platforms, then DataSet offers some excellent features.

As well, several of the decisions you can make will offer various levels of tactical (short-term) versus strategic (long-term) interoperability. The next section deals with those.

Specific SOAP Issues

As of this writing, SOAP interoperability has moved dramatically, and quite far. In particular, there have been a number of advances with respect to interoperability for RPC/encoded style service.

Specifically, the SOAPBuilders Yahoo! Group (http://groups.yahoo.com/group/soapbuilders/) has engaged in a number of rounds of inter-operability testing. Most of the early ones have focused on RPC/encoded SOAP; however, there also has been significant testing of doc/literal, and testing of WSDL. Going to any SOAPBuilders member and examining that member's results against others is usually sufficient research for choosing the right interoperability focus. If you are actually building a SOAP stack, however, it may be useful to think about the various issues that the SOAP-Builders have found.

To begin with, it's important to understand that many SOAP issues actually occur at the basic XML or HTTP level. You need to interoperate at those levels before you can interoperate at the SOAP level.

Typical XML interoperability issues include character set encoding, such as UTF-8 or ASCII, and namespaces and the handling of namespaces. With HTTP, the issues usually are found with HTTP version 1.1 as opposed to HTTP 1.0. There are still several HTTP stacks that either don't do HTTP 1.0, or that do HTTP 1.1 incorrectly. The most common thing to watch out for is lack of keep-alive support.

Most of the problems with Web services interoperability are likely to occur with actual SOAP errors. Section 5 of the SOAP specification, along with Section 7, defines a data encoding that can be used without XML schemas. Even with schemas, Section 5 encoding still is very popular among implementations, but it is also the most likely to be misunderstood and mis-implemented.

For example, Section 5 mentions that you can send the data type of an element with the xsi:type attribute. The message in Listing 15.1 is an example of a method return.

Example 15.1. An Example SOAP Message Using xsi:type

<S:Envelope
     xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<S:Body>
<ns1:echoStringResponse
   xmlns:ns1="http://soapinterop.org/"
   S:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <return xsi:type="xsd:string">Hello, World</return>
</ns1:echoStringResponse>
</S:Body>
</S:Envelope>

This typing information is required only when the data type is itself a polymorphic one. Of course, polymorphism can be hard to define without some kind of out-of-band agreement; however, assuming that both sides think the parameter is a string, this attribute is not required. A few popular implementations have required typing information, but fortunately most of these implementations have newer versions that no longer require this information except for polymorphic types. This is just an example of the kind of interoperability problems that for the most part no longer exist. However, it is still something to keep in mind.

Another, even more fundamental problem involves document and RPC style interfaces. Some toolkits can do only one or the other, and every toolkit has a different default. Apache SOAP is an RPC/encoded toolkit by default, whereas .NET Web Services is document/literal by default. One of the most common problems I've seen is when an Apache client sends an RPC/encoded request message to a .NET server, and the .NET server returns a response, but that response is the response message you would see from a request message that was full of null values. For example, the message in Listing 15.2 could be sent for a simple Add service.

Example 15.2. An Encoded SOAP Message

<S:Envelope
     xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<S:Body>
<ns1:Add
   xmlns:ns1="http://soapinterop.org/"
   S:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <A xsi:type="xsd:int">4</A>
      <B xsi:type="xsd:int">4</B>
</ns1:echoStringResponse>
</S:Body>
</S:Envelope>

However, then the response message from the .NET server is null, instead of 8, as shown in Listing 15.3.

Example 15.3. A Null Document/Literal Response

<S:Envelope
     xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<S:Body>
<ns1:AddResponse
   xmlns:ns1="http://soapinterop.org/" >
</ns1:AddResponse>
</S:Body>

The problem here is that there was formal communication of the style of SOAP operation involved. The WSDL for the .NET server states that it is document/literal. To avoid this problem, I would recommend always using a WSDL description to define both clients and servers. Modifying the .NET server to be an RPC/encoded endpoint for easy interoperation is simple: Just add the [SoapRpcMethod]attribute to the method attribute:

[WebMethod] 
[SoapRpcMethod]
public int Add( int A, int B )
{
     return A + B;
}

Specific WSDL Issues

WSDL interoperability problems are still being discovered. Typically, they revolve around two issues: advanced namespace handling and schema processing. There are certainly other issues, but these are the most common.

There are also two major schema problems. The first problem is lack of support for the 2001 Recommendation of XML Schema. The other problem has to do with the XML Schema processors that WSDL engines use: Many of them don't understand all of the advanced features found in the XML Schema specifications, such as element substitution groups.

By advanced namespace handling, I mean the manner in which WSDL and XML Schema use namespaces, along with the targetNamespace attribute, to refer to some values. To illustrate, a WSDL document will have a specific targetNamespace and a prefix bound to that namespace:

<definitions 
     xmlns:tns="http://tempuri.org/"
     targetNamespace="http://tempuri.org/"
     xmlns="http://schemas.xmlsoap.org/wsdl/">

Now, there are several elements within WSDL that are given names, such as portType:

<portType name="Service1Soap"> 

And this portType element is referred to later with a QName that resolves to the targetNamespace:

<binding name="Service1Soap" type="tns:Service1Soap"> 

This seems simple enough, but several implementations have gotten this wrong at times. Instead of remembering to use the tns prefix, they have given the type a simple NCName. The result is that resolution of the QName won't work.

WS-I Profiles

The Web Services Interoperability organization (WS-I) is a new industry consortium that was formed to ease the burden of achieving interoperability. It is achieving this by creating “profiles” of Web services. These profiles describe a safe subset of SOAP, WSDL, and other specifications that enable developers to design a Web service with a much higher probability of success in terms of interoperability. WS-I is also building sample applications that demonstrate how to create a Web service with these profiles, and a test group is building test tools for verifying a service's adherence to the basic profile.

Versioning

One of the most critical design decisions you can make is dealing with how to version Web services. As with many other distributed technologies, it's very easy to get this wrong, and hurt yourself in the future. This section deals with some of the major versioning-related design decisions to keep in mind when creating the first version of your Web service, and later versions as well.

Types of Versioning

To begin with, what does versioning mean? There are actually several types of possible versioning schemes:

  • Interface versioning

  • Versioning of implementation

  • Versioning of data types

Typically, interface versioning gets the most attention, but it would be a mistake to not consider the other two types. Each has its own implications. More important, SOAP and the Web service architecture allow us to version each of these in a simple manner. It's a beautiful thing when XML's open content model lets you work such magic.

Loosely Coupled Architecture and Implementation Versioning

The most powerful feature in Web services is not interoperability between heterogeneous environments. It is a valuable feature, and the primary reason why most people have migrated toward it. But the more fundamental reason to use Web services is loose coupling. This is a loaded term that means different things to different people. When I refer to loose coupling, I mean that the actual implementation code for the client or server doesn't directly influence the wire format.

Imagine how powerful this is, and how different from previous distributed technologies such as DCOM. Whereas before, the signature of an implementation would have a direct impact on the wire format, now you can completely change your implementation signatures, technologies, or platforms without changing the wire format. The advantage of this is obvious when you consider interoperability, but it also has the benefit of lowering development costs.

Imagine the following scenario: You build a Web service for accepting purchase orders from an important customer. You design it to be document/literal style, and you use the SOAP Toolkit 2.0. Your shop is full of Visual BASIC 6 (VB 6) developers, so this is a natural choice. Over time, however, you may find that you've migrated to a managed code (C# and .NET Framework) development shop. At some point, you will probably find that maintaining the VB 6 code base for your purchasing service isn't cost effective: It's too much time and wasted effort. Luckily, you can move the service to a .NET code base without breaking the wire format that your partners are expecting. This is a large advantage over distributed technologies of the past.

Even within a single platform such as .NET, you may want to change your implementation radically, for reasons ranging from developer fancy, to security, to performance. As a small example, imagine you have a Web method that looks like this:

[WebMethod] 
public string SubmitPO( PurchaseOrder po)
{
     return "Order " + po.Id + " received";
}

The PurchaseOrder class may look like this:

public class PurchaseOrder 
{
     public String Id;
}

The SOAP for the request message would look like this:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> 
  <soap:Body>
    <SubmitPO xmlns="http://tempuri.org/">
      <po>
        <Id>string</Id>
      </po>
    </SubmitPO>
  </soap:Body>
</soap:Envelope>

You can change the signature of the method:

[WebMethod] 
[SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare)]
public string SubmitPO2( SubmitPurchaseOrder SubmitPO)
{
     return "Order " + SubmitPO.po.Id + " received";
}

And the request message stays the same. The response message is different, but the code to keep that the same is just as easy. What's important is that this isn't a .NET feature. This is a feature of Web services. The implementation doesn't dictate the wire format. The implementation of a service or a service client is loosely coupled to the SOAP message that flows over the wire.

Namespaces and Versioning

Put simply, when you version a Web service interface or a data type, you should create a new namespace URI. In fact, all namespace URIs should contain a date, to allow for quick reading to determine which version of a type or interface is being used. For example, instead of the useless http://tempuri.org namespace in our previous example, we could give it a specific namespace with the [WebService] attribute:

[WebService(Namespace="http://foo.com/2002/04/POService")] 
public class PurchaseOrderService

The SOAP request also would change, to the following:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> 
  <soap:Body>
    <SubmitPO xmlns="http://foo.com/2002/04/POService">
      <po>
        <Id>string</Id>
      </po>
    </SubmitPO>
  </soap:Body>
</soap:Envelope>

Now, if we decide to create a new service that takes a purchase order, but also takes another SubmitterID, then we should version our interface to be a new URI. There are many ways to do this in .NET. We'll use the same attribute again:

[WebService(Namespace="http://foo.com/2003/08/POService")] 
public class PurchaseOrderService

And the SOAP message changes as well:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> 
  <soap:Body>
    <SubmitPO xmlns="http://foo.com/2003/08/POService">
      <po>
        <Id>string</Id>
      </po>
    </SubmitPO>
  </soap:Body>
</soap:Envelope>

You could also imagine versioning the PurchaseOrder type itself. In anticipation of that, you could start by giving it a specific URI:

[XmlRoot(Namespace="http://foo.com/2002/4/PurchaseOrder")] 
public class PurchaseOrder
{
     public String Id;
}

This would create a wire format as follows:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> 
  <soap:Body>
    <SubmitPO xmlns="http://foo.com/2002/04/PO">
      <po>
        <Id xmlns="http://foo.com/2002/4/PurchaseOrder">string</Id>
      </po>
    </SubmitPO>
  </soap:Body>
</soap:Envelope>

I would recommend that you always version your data types along with your interfaces. With .NET, this would eliminate the need to put an explicit namespace on each type, because instead you could put it just at the Web method or Web service level. (With .NET, you can use the [XmlRoot] attribute to set namespaces on classes. This would be the easiest way to version.)

Using Business Logic

To help not only with versioning, but also with designing a loosely coupled system, try as much as possible to write code that is independent of the actual wire format. Listing 15.4 shows an example that creates a PurchaseOrderHandler class which uses a PurchaseOrder object. However, the service takes a NewPurchaseOrder class that is designed to match the wire format.

Example 15.4. Changing the Wire Format

public class Service1 : WebService
{
     [WebMethod]
     public POReceipt SubmitPO(NewPurchaseOrder po)
     {
     PurchaseOrder purchaseOrder = new PurchaseOrder();
     purchaseOrder.Details = po.SomethingInteresting;

     POReceipt receipt = new POReceipt();
     receipt.ticket = PurchaseOrderHandler.HandleOrder(
                                        purchaseOrder );
     return receipt;
     }
}
[XmlRoot(
     "PurchaseOrder",
     Namespace="http://keithba.com/2002/05/PurchaseOrder")]
public class NewPurchaseOrder
{
     public String SomethingInteresting;
}

public class PurchaseOrder
{
     public String Details;
}

public class POReceipt
{
      [XmlAttribute]
      public int ticket;
}

public class PurchaseOrderHandler
{
     public static int HandleOrder( PurchaseOrder po )
     {
          //insert real business logic here
     }
}

This allows you to deal with changes to wire format that may occur without having to change your core business logic. Instead, the ASP.NET Web Service is a thin shell over the core business logic.

Caching

Another design tip to keep in mind is to use .NET's caching features. There are two related features in ASP.NET Web Services that enable the developer to cache responses and application data.

The first is called output caching, and it is available right on the [WebMethod] attribute. The CacheDuration property, when set, will remember the response to send for the time limit specified. This means that your logic will not be called. If you know that the logic doesn't need to be called, then this is a good option. Here is an example:

[WebMethod( CacheDuration=60 )] 
public StockData GetStockData()
{
     //insert database query here
}

As you can probably tell, this works best for Web service operations that focus on returning data. An operation that wasn't a get but more a set or update operation wouldn't be a good candidate for output caching.

You can also take advantage of ASP.NET's application caching with the Cache object. This class lets you stuff custom objects into its cache, and then specify specific timeouts or even files or folders to watch for changes to invalidate the cache.

Here is an example of inserting some data:

Cache.Insert("CachedStockData", StockQuoteData, null, 
                    DateTime.Now.AddMinutes(15), TimeSpan.Zero);

You can then look for this value, and if it is not null, use the value found in the cache:

StockQuoteData = Cache["CacheStockData"]; 
if(StockQuoteData != null )
{
      //do something with the data
}

Summary

There are more techniques for designing and implementing Web services than this book can possibly cover. But here are the highlights:

  • Think about performance from day one, but don't get caught up in it. Using asynchronous methods and optimizing the network topology are the best things you can do.

  • Interoperability is best achieved by considering your target audience's SOAP stack, and whether your goal is long-term strategic or short-term tactical interoperability.

  • Versioning is tough to accomplish. When you version interfaces, make sure you choose namespaces with dates in them. .NET makes it easy to match a particular WSDL-defined interface and change the implementation code.

Final Thoughts

Throughout this book, we've examined the Web services architecture. This includes baseline specifications—such as XML, SOAP, and WSDL—and more advanced specifications that provide for advanced features—such as WS-Security and DIME. We've also examined the implementation features found on the .NET platform.

This architecture is a living one, and it is by no means frozen. It will continue to evolve over the next several years. Changes to this architecture— some quite dramatic to meet real-world needs—will take place as the existing specifications develop and new specifications are created as a result of implementation experience and the standards process. However, the architecture as it exists today is already solving problems and is very well suited to building interoperable solutions. As changes occur, I'll keep an update on my Web site (http://www.keithba.com/book).

Happy SOAP-ing!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.19.30.232