Thoughts on Web Service Contracts: The different artifacts
I’m currently thinking about Web Service contracts with the goal to achieve the highest level of compensability and reusability. I like to share some of my thoughts...
First of all, Web Service contracts should be structured into the following four artifacts:
- Common types
- Service/domain specific types
- Service messages
- Service descriptions
- Messages
- Interfaces
- Bindings
- Services
Common types
Common types are represented by global schema elements such as types and elements. They describe enterprise wide entities such us customer, account or product. Note: Entities or entity-views must be uniquely identifiable across the organization; therefore the schema must also contain an element for them (not only a type). This dramatically simplifies entity mapping:
<xsd:complexType name="CustomerType">
<xsd:sequence>
…
</xsd:sequence>
</xsd:complexType>
<xsd:element name="Customer" type="tns:CustomerType" />
It’s a good practice to define references for each global entity:
<xsd:complexType name="CustomerRefType">
<xsd:sequence>
<xsd:element name="name" type="xsi:string" />
<xsd:element name="firstName" type="xsi:string" />
<xsd:element name="id" type="xsi:string" />
</xsd:sequence>
</xsd:complexType>
<xsd:element name="CustomerRef" type="tns:CustomerRefType" />
Service Specific Types
Service specific types are types only relevant to a specific domain (service). They can be compounded of different common types or be defined by their own.
Service Messages
Service messages are described by schema types composing the service specific request, respond or header messages. For each message type the corresponding schema element must be defined. This assures that the message root element is always the same for all messages of the same message type:
<xsd:complexType name="AddRequestType">
<xsd:sequence>
<xsd:element name="a" type="xs:int" />
<xsd:element name="b" type="xs:int" />
</xsd:sequence>
</xsd:complexType>
<xsd:element name="AddRequest" type="tns:AddRequestType" />
Service Descriptions
Service descriptions describe the service endpoints by specifying their message interface and the different supported protocols and transports. Note: For reusability reasons, the service, binding, portType, message and type artifacts must be separated and stored in different physical files. By doing so, different bindings for the same portType can be implemented independent of each other as well as different portTypes can refer to the same messages. The following paragraph describes the different wsdl artifacts and their responsibilities:
x.message.wsdl
The message artifact imports all required service messages as schemas. This must is done by using the xsd:schemaImport node:
<wsdl:types>
<xsd:schema targetNamespace="uri.test">
<xsd:import namespace="uri.test.t" schemaLocation="t.xsd"/>
</xsd:schema>
</wsdl:types>
For each service message there must be a corresponding wsdl:message. The definition of the wsdl message element must only contain one part and this part must not be named parameters (parameters implies wrapped style). Furthermore, the part must reference the schema node using the element attribute (type attributes are used for rpc literal):
<wsdl:message name="AddRequestMsg">
<part name="AddRequest" element="myNs:AddRequest" />
</wsdl:message>
x.interface.wsdl
The interface (portType) artifact defines the operations of a service interface, or in other words, they map request and response message to operation names.
<wsdl:definitions …>
<wsdl:import namespace="test" location="Message.wsdl" />
<wsdl:types />
<wsdl:portType name="AddCalculatorPortType">
<wsdl:operation name="Add">
<wsdl:input message="msgNs:AddRequestMsg" />
<wsdl:output message=" msgNs:AddResponseMsg" />
</operation>
</portType>
…
</wsdl:definitions>
x.binding.wsdl
The binding artifact is the transport and protocol specific implementation of portTypes. Each portType requires at least one binding definition. The binding specifies the transport, style and the message encoding. The style should be set to document (not rpc) and the use must be literal.
<wsdl:binding name="ACBinding" type="ptNs:AddCalculatorPortType">
<soap:binding style="document" transport="mytransport"/>
<wsdl:operation name="Add">
<soap:operation soapAction="uri/add"/>
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
In addition to the soap body, the input as well as the output node can contain headers.
x.service.wsdl
The service artifact is optional. It just maps the endpoint to a binding. This is better done by dynamically lookup services (e.g. UDDI).
The Somewhat Perfect Calculator Contract
The following five files build a composable and reusable contract for a calculator Web Service:
calculator.service.wsdl
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns:http="https://schemas.xmlsoap.org/wsdl/http/"
xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/"
xmlns:bind="uri.beatsch/calculator/Bindings"
xmlns:tns="uri.beatsch/calculator"
targetNamespace="uri.beatsch/calculator"
xmlns="https://schemas.xmlsoap.org/wsdl/">
<import namespace="uri.beatsch/calculator/Bindings"
location="calculator.binding.wsdl" />
<types />
<message/>
<portType/>
<binding/>
<service name="Calculator">
<port name="CalculatorSoapBinding"
binding="bind:CalculatorSoapBinding">
<soap:address location="https://Calculator.asmx" />
</port>
</service>
</definitions>
calculator.binding.wsdl
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns:http="https://schemas.xmlsoap.org/wsdl/http/"
xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/"
xmlns:inter="uri.beatsch/calculator/Interface"
xmlns:tns="uri.beatsch/calculator/Bindings"
targetNamespace="uri.beatsch/calculator/Bindings"
xmlns="https://schemas.xmlsoap.org/wsdl/">
<import namespace="uri.beatsch/calculator/Interface"
location="calculator.interface.wsdl" />
<types />
<message/>
<portType/>
<binding name="CalculatorSoapBinding"
type="inter:CalculatorSoapBinding" >
<soap:binding
transport= "https://schemas.xmlsoap.org/soap/http"
style="document" />
<operation name="Add">
<soap:operation soapAction="uri.beatsch/calculator/Add"
style="document" />
<input>
<soap:body use="literal" />
</input>
<output>
<soap:body use="literal" />
</output>
</operation>
</binding>
</definitions>
calculator.interface.wsdl
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns:http="https://schemas.xmlsoap.org/wsdl/http/"
xmlns:msgs="uri.beatsch/calculator/Message"
xmlns:tns="uri.beatsch/calculator/Interface"
targetNamespace="uri.beatsch/calculator/Interface"
xmlns="https://schemas.xmlsoap.org/wsdl/">
<import namespace="uri.beatsch/calculator/Message"
location="calculator.message.wsdl" />
<types />
<message/>
<portType name="CalculatorSoapBinding">
<operation name="Add">
<input message="msgs:AddRequestMsg" />
<output message="msgs:AddResponseMsg" />
</operation>
</portType>
<binding/>
</definitions>
calculator.message.wsdl
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns:http="https://schemas.xmlsoap.org/wsdl/http/"
xmlns:msgs="uri.beatsch/calculator/MessageTypes"
xmlns:tns="uri.beatsch/calculator/Message"
targetNamespace="uri.beatsch/calculator/Message"
xmlns="https://schemas.xmlsoap.org/wsdl/">
<types>
<xs:schema xmlns:tns="uri.beatsch/calculator/Message"
elementFormDefault="qualified"
targetNamespace="uri.beatsch/calculator/Message"
xmlns:xs="https://www.w3.org/2001/XMLSchema">
<xs:schemaImport
namespace="uri.beatsch/calculator/MessageTypes"
schemaLocation="calculator.message.xsd" />
</xs:schema>
</types>
<message name="AddRequestMsg">
<part name="AddRequest" element="msgs:AddRequest" />
</message>
<message name="AddResponseMsg">
<part name="AddResponse" element="msgs:AddResponse" />
</message>
<portType/>
<binding/>
</definitions>
calculator.message.xsd
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns="uri.beatsch/calculator/MessageTypes"
elementFormDefault="qualified"
targetNamespace="uri.beatsch/calculator/MessageTypes"
xmlns:xs="https://www.w3.org/2001/XMLSchema">
<xs:element name="AddRequest" type="tns:AddRequestType" />
<xs:complexType name="AddRequestType">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="a"
type="xs:int" />
<xs:element minOccurs="1" maxOccurs="1" name="b"
type="xs:int" />
</xs:sequence>
</xs:complexType>
<xs:element name="AddResponse" type="tns:AddResponseType" />
<xs:complexType name="AddResponseType">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="result"
type="xs:int" />
</xs:sequence>
</xs:complexType>
</xs:schema>