WSDL and Contracts: Communicating with Care. SOAP is not flashy, but it runs a big chunk of serious traffic. While the REST debate keeps heating up, teams keep shipping SOAP endpoints on .NET with WCF, on Java with JAX WS, Axis2, and CXF, and behind appliances that love XML. I spent the day reviewing a partner’s WSDL for a billing gateway, and it reminded me that a service is not just code. A service is a contract. When the contract is clear, integration feels boring in the best possible way. When it is sloppy, you pay for it in late nights, random timeouts, and finger pointing over namespaces.
Definitions
WSDL is the machine readable description of a service. It lists types in XSD, messages that carry those types, port types that group operations, and bindings that say how to talk over SOAP and HTTP. A contract is the set of promises your service makes to clients. That includes the WSDL, the XSD, the SOAP rules you follow, your versioning story, and the non written bits like timeouts and auth.
Contract first means you design the WSDL and XSD up front, then generate code. Contract last means you write code and generate the WSDL from it. Document literal is the style most toolkits interop with best, especially when you follow the WS I Basic Profile. Wrapped means a top level element wraps operation parameters in an orderly way.
Examples
Here is a tiny contract first slice that plays nice with common stacks:
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://example.com/billing"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
targetNamespace="http://example.com/billing">
<wsdl:types>
<xs:schema targetNamespace="http://example.com/billing"
elementFormDefault="qualified">
<xs:element name="ChargeRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="AccountId" type="xs:string"/>
<xs:element name="AmountCents" type="xs:int"/>
<xs:element name="Currency" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="ChargeResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="AuthorizationCode" type="xs:string"/>
<xs:element name="Approved" type="xs:boolean"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
</wsdl:types>
<wsdl:message name="ChargeIn">
<wsdl:part name="parameters" element="tns:ChargeRequest"/>
</wsdl:message>
<wsdl:message name="ChargeOut">
<wsdl:part name="parameters" element="tns:ChargeResponse"/>
</wsdl:message>
<wsdl:portType name="BillingPortType">
<wsdl:operation name="Charge">
<wsdl:input message="tns:ChargeIn"/>
<wsdl:output message="tns:ChargeOut"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="BillingBinding" type="tns:BillingPortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<wsdl:operation name="Charge">
<soap:operation soapAction="http://example.com/billing/Charge"/>
<wsdl:input><soap:body use="literal"/></wsdl:input>
<wsdl:output><soap:body use="literal"/></wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="BillingService">
<wsdl:port name="BillingPort" binding="tns:BillingBinding">
<soap:address location="https://api.example.com/billing"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>A test request looks like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:bil="http://example.com/billing">
<soapenv:Body>
<bil:ChargeRequest>
<bil:AccountId>A123</bil:AccountId>
<bil:AmountCents>2599</bil:AmountCents>
<bil:Currency>USD</bil:Currency>
</bil:ChargeRequest>
</soapenv:Body>
</soapenv:Envelope>Counterexamples
Things that cause pain across toolkits: rpc style bindings, xsd anyType in messages, mixed encoding, messages that embed DataSet or custom binary, and missing element qualification. Case in point, a DataSet return turned a neat schema into a blob. Many Java clients could not generate classes at all. Another one used one giant string inside a base64 field and called it flexible. It forced every consumer to roll a parser and guess fields.
<xs:element name="UglyResponse" type="xs:anyType"/>
That single line looks harmless. It invites chaos. Tools give up and push raw XML to your app layer. You end up writing glue forever.
Decision rubric
- Pick contract first for shared services. When many teams will consume it, design the XSD and WSDL up front, review with real clients, then generate stubs.
- Pick contract last for internal point to point. When you own both ends and speed matters, code first can be fine. Keep the surface boring and stick to document literal wrapped.
- Follow WS I Basic Profile. Document literal, qualified elements, no encoded use, clear soapAction, stable namespaces.
- Keep messages small and explicit. One request element that wraps fields beats a maze of parts.
- Version with a namespace plan. New major version gets a new namespace. Minor additions should be optional fields with defaults.
- Stabilize non functional bits. Timeouts, auth scheme, compression, and retry policy are part of the deal. Write them down.
- Generate clients with at least two stacks. If both .NET and Java can consume it cleanly, you are in a good place.
- Automate contract checks. Keep a test that validates the WSDL and schema and runs a golden SOAP call.
Lesson learned
A service contract is conversation design. Speak clearly, use a steady vocabulary, and your partners will ship faster. Leave it vague, and you ship a support queue. Today’s buzz will pass. What stays is a steady contract that people can trust. Start with the contract. Keep it boring. Your future self will send you a thank you note.