oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Web Services: Objects or XML Endpoints?

by Matthew MacDonald, coauthor of Programming .NET Web Services

Recently, there has been a good deal of interest in techniques that allow .NET developers to extend Web services and mimic the behavior of a true object-oriented system. Unfortunately, these coding "workarounds" come with a price. At best, they mask the underlying reality of Web services and encourage the developer to adopt inefficient practices. At worst, they are the foundation for Web services that perform poorly, can't scale, and won't interoperate with other platforms.

The problem is that Web services aren't objects, and pretending that they are blurs the fundamental differences between stand-alone applications and distributed programming. To master Web services on the .NET platform, you'll have to understand more than a little about the underlying SOAP and HTTP reality, and you'll need to adopt a few new programming habits.

Why .NET Web Services Aren't Objects

With the .NET platform, a client can use a Web service in a way that's syntactically similar to using a local class. This is all thanks to .NET's built-in support for Web service proxies. Here's a typical example with a stock quote service:

localhost.StockQuoteService proxy = new localhost.StockQuoteService();
decimal value = proxy.GetStockQuote("MSFT");
Console.WriteLine("MSFT is worth: " + value.ToString());

On the surface, it looks as though the client is communicating directly with an instance of the StockQuoteService class. However, this class is little more than a hollow shell -- a proxy class that .NET generated automatically. It contains the same interface as the StockQuoteService, but no implementation code. Instead, when a method like GetStockQuote() is called, the proxy class packages the request up in a SOAP message, sends it over an HTTP channel, patiently waits for a response from the server, and finally unpacks the .NET message and converts it to the appropriate type for the return value.

Chunky and Chatty Interfaces

"Chatty" interfaces are nothing new for most .NET developers. However, the syntactical similarity between Web service use and ordinary class use can lure a programmer into practices that aren't suited to a distributed environment. For example, it's easy to forget that Web service communication takes, comparatively speaking, a long time -- thousands of times longer than a comparable in-process method invocation. For this reason, it's simply not practical to use a chatty interface that requires multiple method calls for a single task, or (even worse) relies on property procedures. Each separate call to a Web service multiplies the performance hit on the client application, and the load on the network. It's far better to use a "chunky" interface that sends all of the required data in one pass:

// Using a "chatty" Web service.
// (Code to declare customer, order, and item objects omitted.)
localhost.StockQuoteChatty proxyA = new localhost.StockQuoteChatty();


// Using a "chunky" Web service.
// (Code to declare customer, order, and item objects omitted.)
localhost.StockQuoteChunky proxyB = new localhost.StockQuoteChunky();

OrderItem[] items = {itemA, itemB, itemC};
proxyB.AddCompleteOrder(customer, order, items);

Clearly, the second code listing is much less flexible. It requires you to add an additional Web method to allow customer records to be inserted without an order. However, it will also perform better as a Web service. When you are programming effective Web services, this is a case where you need to sacrifice object-oriented programming elegance for sturdy, common-sense programming.

Incidentally, the shift shown in the code above also helps move some of the client-side business logic to the server. In the chatty interface, the client needs to determine if the customer who is making the order is already entered in the database, and insert the record only if required. In the chunky interface, the Web service will perform that lookup and insert the required record, all with the same database connection it uses to add the order and order item records.

State and Other Pitfalls

The overhead of Web service communication isn't the only area where ordinary stand-alone programming and distributed programming clash. Here are some other trouble spots that you should take into account before you begin any Web service design:

  • Web services don't maintain state. You can store information using ASP.NET's session state facility, but it requires the cooperation of the client. Furthermore, all states will be indexed by a simple string key and linked to a session cookie. There is no easy way to tie state to a specific class or an instance of a class.

  • Web services don't provide rich error handling. When you throw an exception from a Web method, the exception is caught on the server side, and some of the information is serialized into a SOAP error message. When the proxy class receives this error message, it throws a SoapException on the client side. The client is still notified of the problem, but gives up the ability to catch specific exception types (like SecurityException or FileNotFoundException).

  • Web services don't support multiple interfaces. Thus, there is only one door through which a client can access a Web service. If you need finer granularity, create server-side objects that perform the work, and wrap them in multiple Web service "front ends," each of which exposes just the functionality required by a single client. This can also help you achieve loose binding between a Web service and its client.

  • Web services don't allow method overloading. This is a limitation in the SOAP standard. It means you can't differentiate methods by their arguments alone. Instead, you'll need to create separately named methods like GetCustomerByID() and GetCustomerByName().

  • Web services return XML, not objects. That means that many .NET types aren't supported. In some cases, they can be serialized to XML, but not successfully reconstructed on the client end. The DataSet is an example of a .NET object that can make the transition. The DataTable and DataRow are two examples that cannot.

  • Web services can't raise events. Instead, communication is always initiated by the client. When a Web method returns, the link is broken, and the Web service instance is destroyed.

Web Services Are More Than Just XML

Of course, Web services aren't just locations on the Web that return SOAP-formatted documents. Retrieving a Web service response is more than just downloading an HTML Web page.

For example, Web services support the seamless serialization of some data types, allowing .NET clients to retrieve a full, versioned DataSet. Web services can also dynamically cache entire responses (output caching) or portions of information (data caching) for reuse on the server side. They also support a declarative programming model that allows you to execute code inside of an automatic COM+ transaction, simply by adding an attribute to a Web method:

[WebMethod(TransactionOption = TransactionOption.RequiresNew)]
public void DoSomething()
{ ... }

These are all real considerations that .NET Web service programmers should try to adhere to. As you design Web services that face the limitations of object-oriented programming as well as the unique features of the .NET platform, you'll find that your classes start to look like stateless helper classes instead of entity representations. This is nothing to fear! Remember, Web services exist to collect and expose useful server-side functionality. They aren't designed to model entities or present true object-oriented abstractions.