Is WSDL the indispensable API?
WSDL could become your universal design view
Web services combine the best aspects of component-based development with the Web’s ubiquity and easy access. Think of Web services as black-box functionality you can reuse without worrying about how the service is implemented—a universal API, in other words. Whereas other middleware offerings limit access via binary, object model-specific protocols such as DCOM (Distributed Component Object Model) or RMI (Remote Method Invocation), Web services can be invoked via ubiquitous Web protocols such as HTTP and data formats such as XML.
Web services stem from three core standards: SOAP (Simple Object Access Protocol), UDDI (Universal Description, Discovery, and Integration), and WSDL (Web Services Description Language).
WSDL, an XML-based language, defines Web services and describes how to access them. The WSDL design view can also ease interoperability between disparate component middleware offerings. In this article, you’ll learn how the WSDL specification offers a language-agnostic view of services, freeing developers from choosing the underlying implementation.
Note: To discuss this article, see Resources.
Middleware: A brief history
Web-enabled system designs traditionally employ a three-tier architecture composed of a client, a service, and data. While the service’s position in this architecture has over time moved between the server and the client depending on available technology, today it firmly resides on the server. The 1990s witnessed an explosion of middleware solutions for the server platform, allowing a central position for business logic to manipulate back-end data.
For a whirlwind tour of middleware development, CORBA at the start of the 1990s represented the first platform-neutral, distributed middleware. CORBA purported to be language agnostic, exposing component interfaces as IDL (Interface Definition Language). Using a CORBA-compliant language, developers could remotely discover such interfaces’ implementations on the network at runtime using a naming service to look up the component that implemented the interface. Although brilliant, only advanced programmers find CORBA suitable because they must worry around the underlying plumbing, such as transactions, concurrency, scalability, and component life-cycle management, forcing business functionality into second place.
Capitalizing on CORBA’s shortcomings, in 1998 Sun Microsystems, in conjunction with other third-party middleware vendors, published the first Enterprise JavaBean (EJB) specification. EJBs introduced distributed components with session beans (components responsible for use cases and business logic), and entity beans (components to manage the data). Each bean describes its methods in remote interfaces, which the bean itself implements. EJBs also introduced containers, a place to deploy beans and manage their life cycle. In contrast to CORBA, containers handle the underlying plumbing, freeing the developer to code the business logic.
All that middleware development produced a myriad of legacy solutions—each requiring its own exotic protocol to access these components. (To be fair, CORBA and EJB are standardizing around IIOP (Internet Inter-ORB Protocol), although IIOP cannot be considered ubiquitous or pervasive.)
A fourth tier?
To let businesses expose their services, or an aggregation of their services, to other business and clients, interoperability and back-end integration of these legacy systems remains the work of costly computer consultants. When XML became a standard in 1998, it ushered in a new paradigm for distributed computing. Despite its hype, XML is simply a meta language—a language for describing other languages. XML for the first time let data be described in a standard way. Over the last three years, XML-related technologies have been standardizing with XSL (Extensible Stylesheet Language) to transform XML and XLL (XML Linking Language) to link it. Finally, when XML Schemas (a way to describe XML in terms of XML) became a standard this month, the final obstacle for XML’s introduction into the middleware platform evaporated. XML now offers a way to describe services and data in a language-agnostic fashion.
Each of the aforementioned component architectures employ an interface to abstract a service description from how the service is implemented. That powerful object-oriented design pattern lets the implementation change while the offered services remain invariant to the change. However, these implementations remain specific to the distributed component architecture they describe: IDL to CORBA and remote interfaces to EJBs.
Describing these services with XML lets you future-proof them, not only in the implementation’s terms, but also in terms of the underlying component architecture. Designing with XML moves away from the process-centric architecture ushered in by CORBA, towards a data-centric architecture. Data centricity lets businesses focus on what data moves around, not what process architecture processes the data.
The WSDL design view
As a core Web services technology, WSDL accomplishes that desired data centricity. Today, existing functionality can be described in terms of WSDL dialect, which future-proofs the application in relation to the implementing component architecture. The specific implementation (CORBA or EJB) is now described in terms of the general (XML and WSDL). WSDL will, in the future, become the design view, while component-specific stubs and skeleton implementations will be generated based on such WSDL. While Microsoft’s .Net does not directly embrace the WSDL design view, that view is integral to the .Net framework. A WSDL file describing a service can still serve as a starting point to generate stubs and skeletons of any .Net implementation languages (C#, VB.Net, and C++).
Where and how then, can we realize the WSDL design view? A rich meta-model solution based on UML (Unified Modeling Language) allows you to graphically model anything from business processes to class relationships in an object-oriented (OO) model. You can also model UML as a meta object facility (MOF). That meta-meta model, a higher UML abstraction, can map to various implementations such as Java, CORBA, or IDL. Most importantly, a mapping exists between the MOF and XMI (meta data interchange).
Take modeling a “make-a-reservation” use case as a simple example. To make a reservation, something must enter the customer’s information and reservation details. A UML analysis will ultimately conclude that a singleton object (there will only be one) must manage a customer reservation’s persistence. In a UML model, the object is implementation agnostic. By mapping the object to XML, you could potentially produce the WSDL to act as a starting point for the stub and skeleton implementation generation. The important point: The modeling remains focused on the business process and delays its implementation in a particular component technology.
For a more complex example, think of aggregating existing and/or new services in a value-added, work-flow manner. Again, you could model such a system in UML, where the existing functionality, expressed in WSDL terms, could be imported into the UML modeling environment, and modeled in an aggregated manner, then exported again as WSDL. Such a just-in-time use case/service implementation represents a powerful asset in an ever-changing IT landscape in which technologies come and go and technologists most often follow. Businesses want to concentrate on these core competences rather than proclaiming themselves either as a J2EE (Java 2 Platform, Enterprise Edition) or .Net house with the resulting IT staff recruitment headaches and culture-building exercises.
The business drivers
With the dot-com industry’s demise, new technologies face tougher adoption cycles. Gone are the days where technology is adopted for technology sake. With that in mind, we should begin this discussion by considering the business cases that make Web services adoption favorable. Before discussing such business cases, however, let’s briefly recap component middleware solutions and the business cases they address.
Today, the programming world largely divides between two programming models: Microsoft’s .Net and Sun’s Java. While Java has long been the OO programming, Sun’s dominance seems threatened by Microsoft’s newly released .Net and its RAD (rapid application development) Visual Studio .Net tool. .Net features Web services native to the platform, while Java is at best trying to retrofit support using the Java APIs for XML. Although both platforms boast near-religious followings, most developers simply want the best tool for the job, such as using .Net on the client and EJBs on the server. To that end, Web services bring interoperability to that disparate arrangement.
Programming with WSDL as the design view lets you future-proof your system’s implementation technologies. That verbose ASCII interface masks the component implementation, letting the programmer employ a best-of-breed approach that until now was the realm of exotic nonsupported APIs like JNI (Java Native Interface).
Analysis and design
Having examined the theory, the following is an example that begins with WSDL, then explains how you can generate component-specific stubs and skeletons from this WSDL file.
Use cases
The example use case, which I first presented in “Add XML to Your J2EE Applications,” (JavaWorld, February 2001) makes a reservation at a car rental agency (Figure 1).
Sequence diagram
To make a reservation, the system must persist a customer or retrieve an existing customer. That rudimentary analysis allows a first-cut sequence diagram, which includes a singleton Reservation Agent object to create and retrieve a customer and create a reservation. For scalability purposes, the singleton object is also stateless, with the client responsible for passing in state with each parameter call. The two repository objects, seen in Figure 2, represent persistence objects for the customer and the reservation objects.
Class diagram
In this example, we’re interested in the customer data object passed while creating or retrieving a customer. The customer will possess an associated name, address, and credit card number. Because the car rental company operates globally, the system anticipates both US and UK customers by creating a base Address
object and two derived types: USAddress
and UKAddress
—standard polymorphic behavior where a person expecting a base Address
type can use either a USAddress
or a UKAddress
instead, as Figure 3 shows.
The reservation object proves much simpler. It contains only string attributes of a previously persisted customer name and his or her car type, start date, and end date. In a typical client/server distributed system, the ReservationStateHolder
and the CustomerStateHolder
objects pass across whatever wire protocol the distributed architecture supports. In the past these wire protocols (IIOP, DCOM, and so on) were all binary based, with IIOP as the only standards-based protocol. Because they are binary, you cannot watch as the data passes to the wire; however, with XML-based Web services, interoperability issues can easily be addressed via the readable nature of the wire protocol. I should also mention the Web services community’s unprecedented success at rapidly consolidating interoperability.
WSDL overview
The WSDL charter says:
“[WSDL is] an XML format for describing network services as a set of endpoints operating on messages containing either document-oriented or procedure-oriented information. The operations and messages are described abstractly, and then bound to a concrete network protocol and message format to define an endpoint. Related concrete endpoints are combined into abstract endpoints (services). WSDL is extensible to allow description of endpoints and their messages regardless of what message formats or network protocols are used to communicate.”
WSDL can now describe these services in a verbose ACSII fashion independent of any implementation. You can produce such WSDL from a UML diagram (when UML tools begin to support this) or, as in this case, from existing code (a stateless session EJB remote interface) that has gone through a WSDL generator, which created the Reservation Agent’s WSDL file. The WSDL file includes five sections, whose relationships you can see in Figure 4.
The WSDL document divides into two groups. The top group includes abstract definitions, while the bottom group consists of concrete descriptions. The abstract sections define SOAP messages in a language- and platform-independent manner. On the other hand, the concrete descriptions define site-specific matters such as serialization.
First, let’s examine abstract definitions. There are three sections:
- Types: Machine- and language-independent type definitions that define user-defined data types. (Here XML Schema specifications define the user-specified types like
CustomerStateHolder
and their corresponding dependencies.) - Messages: Contains function parameters, in/out. For example, for the in/out parameter for
getCustomer()
, the in parameter is a string XML Schema data type, specifying the customer name, whereas the out parameter is aCustomerStateHolder
, a user-defined data type. - PortTypes: Refers to message definitions in the
Messages
section to describe function signatures, for example, operation names such asgetCustomer()
with the corresponding in/out parameters defined in the Messages section.
Then there are the concrete descriptions:
- Bindings: Specifies the binding(s) of each operation in the PortTypes section. For example, SOAP remote procedure call (RPC), HTTP GET, HTTP POST, and so on. (The
getCustomer()
is bound to the SOAP RPC binding.) - Services: Specifies each binding’s port address, which is an endpoint, typically a URL, that services the request. Typically, there can be multiple endpoint Types; that is, the physical location that deals with this request. Here we specify the URL as the endpoint.
Implementation
The above WSDL file represents not only the contract between the client and the server, but also between the server and the client. Starting with the WSDL document, you can generate language-dependent stubs and skeletons. The examples below show how to produce these stubs and skeletons using Java on the server and Java and C# on the client.
Server proxies: Java server
First produce the code’s server-side skeleton to give something to test against. Think of the WSDL as a bidirectional contract. A commercial RAD tool produces, using the car-rental reservation agent WSDL file, the following skeleton class:
/**
* Generated by Cape Studio WSDL Assistant WSDL Assistant Copyright (c) 2001,2002 Cape Clear Software Ltd.
* File: ReservationAgentBeanBindingServer.java
* Creation Date: Wed, Mar 20, 2002 at 15:57:57 GMT
*/
package CarRental;
/** Skeleton Server Implementation-ReservationAgentBeanBindingServer */
public class ReservationAgentBeanBindingServer implements
ReservationAgentBeanBindingServerInterface
{
CustomerStateHolder customer; // A Customer StateHolder Object
public void setCustomer(CustomerStateHolder customer) throws
java.rmi.RemoteException
{
// Set the CustomerStateHolder Object
this.customer = customer;
System.out.println("[ReservationAgentBeanBindingServer]
- setCustomer");
}
public CustomerStateHolder getCustomer(java.lang.String aname)
throws java.rmi.RemoteException
{
// Create a CustomerStateHolder Object
Name name = new Name("eoin");
Address address = new UKAddress("br3 5je");
Person person = new Person(name, address);
CustomerStateHolder customerLocalcopy = new
CustomerStateHolder("1234", 1, person);
System.out.println("[ReservationAgentBeanBindingServer]
- getCustomer");
return customerLocalcopy;
}
public java.lang.String ping() throws java.rmi.RemoteException
{
// Provide a simple ping implementation
System.out.println("[ReservationAgentBeanBindingServer]
- ping");
return "ping back at you";
}
public void removeCustomer(java.lang.String name) throws
java.rmi.RemoteException
{
System.out.println("[ReservationAgentBeanBindingServer]
- removeCustomer");
}
public ReservationStateHolder getReservation(java.lang.String
reservationNumber) throws java.rmi.RemoteException
{
System.out.println("[ReservationAgentBeanBindingServer]
- getReservation");
return new ReservationStateHolder();
}
public java.lang.String setReservation(ReservationStateHolder
reservation) throws java.rmi.RemoteException
{
System.out.println("[ReservationAgentBeanBindingServer]
- setReservation");
return "";
}
}
The server skeleton implementation proves simple. The implementation code for the getCustomer()
, setCustomer()
, and the ping()
methods is also included. In setCustomer()
, a CustomerStateHolder
object is set as a member variable in the class. The getCustomer()
method creates and returns a new CustomerStateHolder
object. Finally, the modified ping()
method returns the string, "ping back at you ..."
I leave the getReservation()
, setReservation()
, and removeCustomer()
method implementations as exercises for the reader.
Stateless session EJB example
You can also generate an EJB server skeleton with the WSDL file and the Business Object pattern. Furthermore, you can reuse the Java server skeleton’s code. The Business Object pattern separates the EJB technical contract from its functional contract, as shown in Figure 5.
Client proxies
The following main Java program talks to the backend server with a generated proxy:
/**
Generated by Cape Studio WSDL Assistant
WSDL Assistant Copyright (c) 2001,2002 Cape Clear Software Ltd.
File: ReservationAgentBeanBindingClientMainline.java
Creation Date: Wed, Mar 20, 2002 at 16:58:19 GMT
*/
package CarRental;
/**
Client Impl - ReservationAgentBeanBinding ClientMainline.java
*/
public class ReservationAgentBeanBindingClientMainline {
public static void main( String[] args ) {
System.out.println( "CapeStudio Generated Mainline" );
System.out.println( "Endpoint = " +
ReservationAgentBeanBindingClientFactory.
getDefaultEndpoint( ) );
try {
ReservationAgentBeanBindingClient client =
ReservationAgentBeanBindingClientFactory.create( );
{
System.out.println( "invoke setCustomer" );
// Create a CustomerStateHolder object
Name name = new Name("eoin");
Address address = new UKAddress("br3 5je");
Person person = new Person(name, address);
CustomerStateHolder customerLocalcopy = new
CustomerStateHolder("1234", 1, person);
// Invoke the setCustomer method with the
// CustomerStateHolder Object
client.setCustomer( customerLocalcopy );
}
{
// Invoke the setCustomer() method
System.out.println( "invoke getCustomer" );
CustomerStateHolder result = client.getCustomer( "eoin" );
// From the CustomerStateHolder object get the card
// number, name and address
System.out.println("getCustomer Card Number = " +
result.getcardNo() );
System.out.println("getCustomer Name = " +
result.getperson().getname().getname() );
System.out.println("getCustomer PostCode = " +
((UKAddress)result.getperson().getaddress()).getpostCode());
}
{
System.out.println( "invoke ping" );
java.lang.String result = client.ping( );
System.out.println( "ping result = " + result );
}
{
System.out.println( "invoke removeCustomer" );
client.removeCustomer( "" );
}
{
System.out.println( "invoke getReservation" );
ReservationStateHolder result = client.getReservation( "" );
System.out.println( "getReservation result =
" + result );
}
{
System.out.println( "invoke setReservation" );
java.lang.String result = client.setReservation( new
ReservationStateHolder( ) );
System.out.println( "setReservation result = " + result );
}
System.out.println( "Run Successful" );
} catch( Throwable t )
{
System.out.println( "Run UnSuccessful" );
t.printStackTrace( System.err );
}
}
}
Again, you can generate the client proxy for the WSDL file. The following main program invokes the server with the proxy by using a Factory pattern to return an instance to the proxy:
ReservationAgentBeanBindingClient client =
ReservationAgentBeanBindingClientFactory.create( );
You can then invoke methods on the server using the proxy. The proxy masks the complexity involved in the marshaling and unmarshaling required for remote invocation of the server using, in this case, SOAP/HTTP. The code above shows simple invocations for getCustomer()
, setCustomer()
, and ping()
.
Produce .Net and other clients
The WSDL code can also generate a host of other clients. As an example, think of a JSP (JavaServer Pages) client, where you deploy a war file in a servlet container to test the client. Other examples include Visual Basic, JScript, or even Perl clients. Finally, let’s employ a Microsoft C# client to test interoperability between Java and .Net.
Using the Microsoft .Net Software Development Kit (SDK), the following command produces a simple C# proxy used by a fat client (Windows Forms), a thin Web client (ASP.Net), or, in the case of the example below, a command-line client. These C# clients can then invoke the Java/EJB-implemented service, providing, as never before, a standards-based bridge between these two culturally and architecturally diverse technologies:
wsdl :/l /protocol:SOAP ReservationAgent.wsdl
The surprisingly simple command-line syntax above produces a C# proxy client—the wsdl
command’s default behavior. However, with the /server
flag included, it produces server-side skeletons from the WSDL file. The other flags include:
/l:CS:
Tells the compiler that the generated client-proxy code should be C#/protocol:
Tells the complier that the proxy will talk to SOAP/HTTP
Next, a simple command-line main C# program invokes the Web service with the generated proxy:
using System;
/// <summary>
/// Summary description for Reservation Agent Client.
/// </summary>
public class Class1
{
public static void Main(){
// Creates an instance of the proxy class
ReservationAgentBeanBinding res = new ReservationAgentBeanBinding();
// Creates a CustomerStateHolder Object
CustomerStateHolder customer = new
CustomerStateHolder();
Person person = new Person();
Name name = new Name();
Address address = new UKAddress();
address.address = "br3 5je";
name.name = "eoin1";
person.name = name;
person.address= address;
customer.person=person;
customer.cardNo="123456";
// Sets a customer
res.getCustomer(customer);
// Gets a customer
CustomerStateHolder csh1 =
res.getCustomer(person.name.name);
Console.WriteLine(csh1.getperson().getname().getname());
}
}
Bridge over troubled waters
You can now consider the WSDL design view a universal API. It clearly shows a language- and component-agnostic view of the myriad programming-language paradigms currently available to developers. Whatever the future holds for Web services, this agnostic view will facilitate interoperability, both by bridging disparate middleware component technologies like J2EE, CORBA, and DCOM, and by easing client/server implementation by allowing developers to use best-of-breed technologies at each stage in an n-tier platform.
What is the future of Web services? Until standards currently underdevelopment such as security, workflow, and transactions, have been finalized, the Web services paradigm will remain incomplete. As for WSDL, from where will developers produce WSDL files in the future? As discussed, UML seems a logical choice. Indeed, big UML tool vendors likely will incorporate the WSDL design view into their products over time, and developers will soon begin designing with WSDL in mind.