The Whys and Hows of Exposing a SOAP Service Using Your REST API
This blog is the fifth part of the series called “API Transformer Recipes”. The series aims to highlight numerous ways in which developers can integrate API Transformer into their workflow in order to gain access to a wide range of tools and frameworks. Hopefully, it should eliminate any assumptions that they might have about being restricted to a particular set of tools just because they use a certain API specification format.
You can find numerous debates out there on the topic of “SOAP vs REST” including those that believe REST is the new SOAP. Depending on your service and its target consumers, it might actually be a good idea (albeit rarely) to provide that service in the form of both SOAP and REST APIs. Sounds like an awful load of work? Perhaps not. Let’s dig deeper.
You can also check out one of my earlier blogs on a similar topic where I shared some tips on migrating your SOAP APIs to REST.
Is REST not enough?
A lot of businesses today have adapted the RESTful approach for their services as it provides a more flexible, lightweight, and efficient solution as compared to the other available alternatives. While this holds true for most cases, there can be times when you’ll find your clients (especially enterprises) seeking a SOAP service to integrate with. Some of the reasons for that can be:
- Being slow adopters, enterprise clients might be reluctant to shift to REST since it is still a relatively newer approach to things and differs greatly from the rigid contract-based approach used by SOAP.
- Tools and infrastructure used by enterprise clients may not yet support REST well.
Keeping this in mind, it may be useful to expose your REST service partially/completely in the form of a SOAP service as well.
Real-life Use Cases
Salesforce provides some APIs as both SOAP and REST allowing developers to integrate in a way that suits them best e.g. check out their Tooling API.
One of our clients at APIMatic, CDQ AG, is a data-centric company. Their RESTful services provide a link between their cloud services and business applications. To facilitate smooth integration for enterprise customers, whose infrastructure does not support REST well, they have exposed the same APIs as SOAP services/WSDL interfaces too. This WSDL serves as a contract for both parties. To keep their REST and SOAP APIs in sync, they have integrated API Transformer into their workflow which helps generate a new version of the WSDL for every change in their REST API (more on this later). With the newer WSDL, they update their SOAP service accordingly, and in order to access the latest changes their service consumers also eventually update their applications based on this latest WSDL.
SOAP and WSDL
While it is not mandatory for every SOAP service to have a WSDL file associated with it, it is widely used as a contract between the SOAP service provider and its consumer. This file provides a complete definition of how the service works, the various operations involved, and other fine grain details of all elements and attributes involved. Many tools exist that let you generate method stubs in almost any language if you have the WSDL file with you.
From REST to SOAP, Using API Transformer
One of the less obvious but distinctive features of API Transformer is its ability to convert REST APIs to SOAP by generating WSDL files from popular formats used to describe REST APIs like OpenAPI/Swagger, RAML, API Blueprint, etc. We’ve seen, on average, over 50 unique transformations (unique per user) to WSDL every month for the past three years since this feature was launched.
Generating WSDL from REST — How does this work?
I went ahead and created a sample OpenAPI
v3.0 file which I then converted to WSDL using API Transformer. Using these files, I will now show you the inner workings of the conversion below. If you are interested to see the complete files, you can find them here.
1. API Information
During the conversion, details related to the API like its identifying title, description, and server URLs are extracted from the OpenAPI file and placed in the relevant service metadata of WSDL.
openapi: 3.0.0 info: title: HelloService description: Swagger file for generating WSDL version: '1.0' servers: - url: https://www.example.com/SayHello/
<service name="HelloService"> <documentation>Swagger file for generating WSDL</documentation> <port name="default_Port" binding="wsdl:HelloService_Binding"> <soap:address location="https://www.example.com/SayHello/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" /> </port> </service>
2. OpenAPI Paths
Generally, the relative paths in OpenAPI help identify RESTful resources, and associated with each of these paths are the various operations possible on these resources. However, these paths have no significance in SOAP/WSDL because the concept of resources is restricted to RESTful APIs only. Only their associated operations are converted to WSDL operations.
3. OpenAPI Operations vs WSDL OperationsWSDL operations are defined in an abstract way inside the port types and their concrete details are provided in the bindings. Information from OpenAPI operations is loaded into both these port types and binding operations.
/message: get: tags: - Messages description: View message entry operationId: GetMessage parameters: - name: messageId in: query required: true schema: type: string responses: 200: description: Message object against the id, if found content: application/json: schema: $ref: '#/components/schemas/Message' 404: description: No messages found content: application/json: schema: $ref: '#/components/schemas/Error'
<portType name="HelloService_PortType"> <operation name="GET_GetMessage"> <documentation>View message entry</documentation> <input message="wsdl:GET_GetMessage_InputMessage" /> <output message="wsdl:GET_GetMessage_OutputMessage"> <documentation>Message object against the id, if found</documentation> </output> <fault name="GET_GetMessage_404" message="wsdl:GET_GetMessage_404"> <documentation>No messages found</documentation> </fault> </operation> </portType> <binding name="HelloService_Binding" type="wsdl:HelloService_PortType"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" /> <operation name="GET_GetMessage"> <soap:operation soapAction="GET_GetMessage" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" /> <input> <soap:body use="literal" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" /> </input> <output> <soap:body use="literal" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" /> </output> <fault name="GET_GetMessage_404"> <soap:fault use="literal" name="GET_GetMessage_404" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" /> </fault> </operation> </binding>
An OpenAPI operation has a distinct HTTP verb associated with it e.g.
DELETE, etc. indicating the type of functionality that is expected to be performed on the resource. All OpenAPI operations are converted to
POST methods for SOAP/WSDL conversion.
WSDL Operation Name
The operation name for WSDL is generated by combining the HTTP verb and operation id/name from the OpenAPI operation and serves as a unique identifier.
All associated request parameters of an OpenAPI operation are wrapped in a single new input type and sent in the form of an input message in the SOAP body. The wrapper input type’s name is generated by combining the original HTTP verb of the operation with the operation’s id and a postfix indicating it as the input type.
Unlike JSON, a root XML element cannot be an array. So even if only a single parameter is involved, a wrapper type helps preserve all type-related information of it including array representations that would otherwise be lost.In the WSDL example above, note the use of the
<input>that provides a link to the concrete
<message>definition in WSDL.
The first success response definition (of the range 2XX) is also wrapped in a new output type which defines the body of the output message received. The type name is generated in a similar fashion as that of the input type except for the postfix which indicates it as an output type.
Error responses of the range 400 and above are considered SOAP faults. Note, however, that the response code has no significance in WSDL.
Again, note the use of the
message attribute in
<fault> that provides a link to the concrete
<message> definition in WSDL.
SOAP Binding Transport Protocol
SOAP binding transport protocol is assumed to be HTTP for the conversion. Since SOAP supports other transport protocols as well, this can be modified manually if required.
4. WSDL Messages
WSDL messages defined using the
<message> tag help describe the data being exchanged between the service provider and the client. Request messages are associated with the
<input> tag while response messages are associated with the
<fault> tag. Different parts of a message define its components and help provide a link to their concrete type schema definitions.
<message name="GET_GetMessage_InputMessage"> <part name="GET_GetMessage_InputMessage" element="schemas:GET_GetMessage_InputMessage" /> </message> <message name="GET_GetMessage_OutputMessage"> <part name="GET_GetMessage_OutputMessage" element="schemas:GET_GetMessage_OutputMessage" /> </message> <message name="GET_GetMessage_404"> <part name="GET_GetMessage_404" element="schemas:GET_GetMessage_404" /> </message>
5. OpenAPI Schema and XML Schema
Schema definitions from OpenAPI are added to the XML schema(s) in WSDL
<types> section. These not only include the types defined globally in OpenAPI
schema definitions but also additional types that represent the wrapped request/response messages.
In OpenAPI, you can fine-tune your schema definitions to represent XML types by adding XML metadata like information about namespace, prefix, XML node name, whether the property is an attribute or not, etc. Let’s have a look at how an OpenAPI schema defined with these attributes translates to a type in WSDL during the conversion.
Message: title: Message required: - from - to - text - date type: object properties: from: type: string to: type: string text: type: string date: type: string format: date-time id: type: string xml: name: id attribute: true xml: name: MessageEntry namespace: https://www.example.com/message prefix: m
<types> <xs:schema xmlns:tns="https://www.example.com/message" targetNamespace="https://www.example.com/message" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="MessageEntry"> <xs:sequence> <xs:element minOccurs="1" name="from" type="xs:string" /> <xs:element minOccurs="1" name="to" type="xs:string" /> <xs:element minOccurs="1" name="text" type="xs:string" /> <xs:element minOccurs="1" name="date" type="xs:dateTime" /> </xs:sequence> <xs:attribute name="id" type="xs:string" /> </xs:complexType> </xs:schema> </types>
- The XML name
MessageEntryspecified in the OpenAPI schema overrides the schema’s original name
Messageand is used instead, when creating the XML schema type in WSDL.
- The boolean
attributeflag in property
idhelped create an XML attribute of the same name as can be seen from the above example.
- Furthermore, notice how in the example above, the
namespaceinformation from the OpenAPI schema helped us place the type definition in
<schema>of the same namespace in WSDL. The prefix assigned to this namespace
mis also preserved in the root tag of WSDL.
<definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:schemas="https://www.example.com/SayHello/schemas" xmlns:wsdl="https://www.example.com/SayHello/wsdl" xmlns:m="https://www.example.com/message" xmlns:e="https://www.example.com/error" targetNamespace="https://www.example.com/SayHello/wsdl" xmlns="http://schemas.xmlsoap.org/wsdl/">
Let’s now see how XML metadata specified in request/response schema translates to a type in WSDL:
responses: 200: description: List of all message objects content: application/json: schema: type: array items: $ref: '#/components/schemas/Message' description: List of all message objects xml: name: Messages wrapped: true
<types> <xs:schema xmlns:tns="https://www.example.com/SayHello/schemas" targetNamespace="https://www.example.com/SayHello/schemas" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="GET_GetMessages_OutputMessage"> <xs:sequence> <xs:element name="Messages"> <xs:complexType> <xs:sequence> <xs:element minOccurs="1" maxOccurs="unbounded" name="response" type="m:MessageEntry"> <xs:annotation> <xs:documentation>List of all message objects</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:schema> </types>
You have the WSDL — What next?
Having a WSDL basically means, you have a skeleton of what your SOAP service will look like. Various tools and frameworks can then help you accelerate the actual implementation of the SOAP service by generating stubs. However, before you start implementing, you need to decide whether you plan to keep the SOAP service independent from your existing REST service or not. The former case will have a bigger maintenance cost and it will be difficult to keep both services in sync when changes occur. The recommended way is to implement this SOAP service more as a proxy service that handles SOAP payloads but converts them to those compatible with your existing REST service. This way your actual service will still be the REST service while the SOAP service will help facilitate the smaller chunk of your customers like enterprises. What option you pick is something you are the best judge of.
REST or SOAP or both? I hope this article offered some clarification in this regard. Ultimately the choice really depends on your service and your target customers. Once you’ve made the decision, there are various tools like API Transformer available to help you get started.
Continue reading more API Transformer Recipes: