In the previous post, I discussed what integration is and presented one of the real-world scenarios we might encounter when implementing an ESB in an enterprise. In this post, we will discuss the step-by-step implementation of what was discussed in the previous post using Mule ESB as an example. The purpose of this post is to present and discuss the implementation of services on Mule based on the three-tier architecture that we discussed in the previous post.
sap-channel
The first sap-channel application, as I mentioned earlier is an old SOAP-based interface, because that was the project requirement --- to preserve the SOAP API for SAP.
What is important for us is the data model defined in the WSDL file:
1<wsdl:types> 2<xsd:schema targetNamespace="http://Example.org" elementFormDefault="qualified" > 3<xsd:element name="GetAccount"> 4<xsd:complexType> 5 <xsd:sequence> 6 <xsd:element minOccurs="0" name="account_id" type="xsd:string" /> 7 </xsd:sequence> 8</xsd:complexType> 9</xsd:element> 10<xsd:element name="GetAccountResponse"> 11<xsd:complexType> 12 <xsd:sequence> 13 <xsd:element minOccurs="0" name="account_id" type="xsd:string" /> 14 <xsd:element minOccurs="0" name="account_name" type="xsd:string" /> 15 <xsd:element minOccurs="0" name="account_phone" type="xsd:string" /> 16 </xsd:sequence> 17</xsd:complexType> 18</xsd:element> 19</xsd:schema> 20</wsdl:types>
This model differs from our canonical one in naming conventions and the fact that it is based on XML. According to the diagram, an important point in the implementation of our channel layer will be not only the service call on the business layer, but also the correct response mapping.
The screen above shows the implementation in Mule ESB of the flow for sap-channel. Below we will discuss the most important parts of it:
Api-main is the flow responsible for publishing the SOAP interface based on the WSDL file. What is important is the SOAP Router component responsible for redirecting depending on the called operation to the correct flow with the implementation. In our case, this is the GetAccount operation defined below:
1<wsdl:message name="IAccount_GetAccount_InputMessage"> 2 <wsdl:part name="parameters" element="tns:GetAccount" /> 3 </wsdl:message> 4 <wsdl:message name="IAccount_GetAccount_OutputMessage"> 5 <wsdl:part name="parameters" element="tns:GetAccountResponse" /> 6 </wsdl:message> 7 8 <wsdl:portType name="IAccount"> 9 <wsdl:operation name="GetAccount"> 10 <wsdl:input wsaw:Action="http://Example.org/IAccount/GetAccount" message="tns:IAccount_GetAccount_InputMessage" /> 11 <wsdl:output wsaw:Action="http://Example.org/IAccount/GetAccountResponse" message="tns:IAccount_GetAccount_OutputMessage" /> 12 </wsdl:operation> 13 </wsdl:portType> 14 15 <wsdl:binding name="DefaultBinding_IAccount" type="tns:IAccount"> 16 <soap:binding transport="http://schemas.xmlsoap.org/soap/http" /> 17 <wsdl:operation name="GetAccount"> 18 <soap:operation soapAction="http://Example.org/IAccount/GetAccount" style="document" /> 19 <wsdl:input> 20 <soap:body use="literal" /> 21 </wsdl:input> 22 <wsdl:output> 23 <soap:body use="literal" /> 24 </wsdl:output> 25 </wsdl:operation> 26 27 </wsdl:binding> 28 <wsdl:service name="AccountService"> 29 <wsdl:port name="IAccount" binding="tns:DefaultBinding_IAccount"> 30 <soap:address location="http://Example.org/IAccount" /> 31 </wsdl:port> 32 </wsdl:service>
In the simplified implementation itself, we have 3 steps:
set account id --- is responsible for assigning an account_id value to the message payload. Step implemented using Data Weave.
/account --- calling a REST service within the business-account application.
Response-mapping --- transformation of the response from business-account to WSDL-compliant XML.
webapp-channel
The second webapp will use our new REST API based on a canonical model in JSON.
The services have been defined within a RAML file and published within the webapp-channel application. Below is its simplified definition and also its implementation within the Mule ESB.
1#%RAML 1.0 2title: Webapp API 3 4types: 5 Account: 6 type: object 7 properties: 8 name: string 9 phone: string 10 id: string 11 12/account/{id}: 13 get: 14 responses: 15 200: 16 body: 17 application/json: 18 type: Account
Analogous to the sap-channel application, we have HTTP input responsible for issuing the GET service /account/{id} and APIkit for proper routing to the flow executing the implementation. In our scenario, we assume that the webapp operates on a canonical model in JSON, where there is no need to perform mapping between models hence the implementation to call the GET /account{id} operation from the business-account application without return mapping.
business-account
The business-account application as intended is to expose a canonical interface and perform only service orchestration.
The application receives an HTTP GET, makes a service call to retrieve account data from the salesforce system using the salesforce-adapter application. Then, as part of the "check status" step, we check whether the account data has been returned. If yes, we return a response, otherwise we retrieve the account data from the database system.
salesforce-adapter
The first of two applications responsible for integrating with domain systems. The task of this application is to receive HTTP GET operations from the business layer, build an output request to Salesforce, and parse the response into a canonical model in JSON.
The first important step in the flow is to perform a SELECT operation in Salesforce. This is accomplished using the built-in connector. With the built-in DataSense mechanism, we retrieve the metadata of the objects from Salesforce on which we will work. Downloading this data gives us the ability to build queries using the Query Builder tool and the ability to easily map in the "map to json" step because we have imported the return object data model from Salesforce.
The next step is to verify that Salesforce has returned a response. If the data is retrieved, we map from the Account object returned by Salesforce to the canonical response in JSON.
If not, we can generate an exception using the Groovy component, which will cause us to return HTTP 404 Resource not found.
throw new org.mule.module.apikit.exception.NotFoundException()
Generating this exception will return a 404 status to business-account based on which flow will decide to call the database-adapter application additionally.
database-adapter
The second of the two domain applications. Its task is identical to that of salesforce-adapter. The application is to retrieve account information from the database based on the passed id and generate a response in the canonical model in JSON.
As with Salesforce, here we have the option to use a built-in connector that uses DataSeanse to retrieve the data model.
We have discussed one of the very typical integration scenarios you may encounter in an ESB implementation project. In a model implementation, the interface of services published on the ESB would be presented to client systems (API consumers) during the analysis stage, and all subsequent implementation would be focused on implementing the service catalog and integrating Mule with domain systems. Such projects do happen, I myself have had the pleasure of participating in two such projects, but most decisions to implement an integration layer are made in realities where systems in the enterprise have existed for many years and the ESB is meant to improve some things, clean up others, introduce some predictability in the design of future changes and functionality. Under such conditions, we can't do the model implementation often discussed at conferences, but we can, with the help of ESB, significantly clean up the reality faced by those responsible for IT development in the enterprise.
All posts in this series:
- [Introduction to systems integration based on Mule ESB]({{ "/introduction-to-system-integration-with-mule-esb/" | prepend: site.baseurl }})
- [Introduction to systems integration based on Mule ESB (part 2)]({{ "/introduction-to-system-integration-with-support-o-mule-esb-part-2/" | prepend: site.baseurl }})