Most of the business organizations use various types of software systems to automate their business processes. Since end users of those business organizations interact with these software systems, these software systems should contain the key business decisions. However the business decisions always subjected to change hence it should be possible to maintain business decisions included within the software systems up to date. Business rules allow software developers to isolate the business decisions related code from the rest of the code by specifying the business decisions as a set of rules in a separate configuration file. Further, these business rules are written using declarative languages instead of procedural languages, making it even readable for business analysts.
Service Oriented Architecture (SOA) is the most prominent architectural pattern used to integrate heterogeneous systems. Therefore if the business decisions written as rules can be exposed as services then business rules can also be integrated to SOA systems. WSO2 BRS can be used to expose business rules as web services. Business Process Execution Language (BPEL) is the standard for integrating web services. This language even support long running processors by persisting the process state. WSO2 BPS can be used to deploy such BPEL process as an integrated web service. This article describes such a sample system which shows how to integrate business rules with SOA system.
A previous article titled as 'Integrate Business Rules with SOA' shows how a customer order processing system can be built by using the WSO2 ESB as the integration layer. WSO2 ESB uses the Apache Synapse as its back end runtime. Therefore the integration language or syntax is Apache synapse specific. Further, Apache Synapse has introduced some mediators which can directly invoke system resources to improve the performance. However Apache Synapse configuration language is not a standard hence can not be used some where else.
This article shows how the exact system can be built using WSO2 BPS which uses Apache ODE as the back-end runtime. WSO2 BPS services are written using BPEL which is a standard and also a more structured language compared to Apache Synapse. BPEL uses a concept called partner links to integrate the web services. These partner links are WSDL based and hence all the integration services should have a WSDL. Therefore in addition to the services used in the earlier sample another service is used to expose the database table data as a web service. WSO2 Data Service Server (DSS) can be used to create such web services which expose data as a web service.
Initially, 'CustomerOrderService' which is the BPEL process, receives the request message which consists of customer details and a set of orders which customer is going to place. Then it sends the order details to the 'PriceCalculatorService' to calculate the price of each order. 'PriceCalculatorService' uses a set of rules to calculate the price of the order according to the number of items. After getting the price of each order 'CustomerOrderService' invokes the 'CustomerDetailService' to find the maximum amount and the rating of the customer using customer name. Next it invokes the 'OrderSelectorService' with the above parameters and the order prices to get the selected order. 'OrderSelectorService' also uses a set of business rules to find the best order for the customer. Finally, 'CustomerOrderService' returns the selected order to its client.
Since this article users the same WSDL for both 'CustomerOrderService' BPEL process and proxy service and exact same rule service all the request and response messages are similar in earlier article. Therefore this article only describes only the 'CustomerDetailService' which is the new data service and BPEL process files. Complete sample code can be downloaded from here.
This service is used to expose the customer table data as a web service and it is written as a data service. Data services uses a special file format to describes the input parameters and out put parameters to the service. First it declares the Data base connection details and then the query and operation details. Query is written as an SQL query and input parameters to the query is specified as param elements and out put format is specified within the result element.
<data name="CustomerDetailService" enableBatchRequests="false" enableBoxcarring="false" serviceStatus="active"> <config id="customerDataSource"> <property name="org.wso2.ws.dataservice.driver">com.mysql.jdbc.Driver</property> <property name="org.wso2.ws.dataservice.protocol">jdbc:mysql://localhost:3306/DATASERVICE_SAMPLE</property> <property name="org.wso2.ws.dataservice.user">stratos</property> <property name="org.wso2.ws.dataservice.password">stratos</property> <property name="org.wso2.ws.dataservice.minpoolsize"></property> <property name="org.wso2.ws.dataservice.maxpoolsize"></property> <property name="org.wso2.ws.dataservice.validation_query"></property> </config> <query id="customerSelectQuery" useConfig="customerDataSource"> <sql>select RATING_C,MAX_AMOUNT_C from CUSTOMER_T where NAME_C=:name</sql> <result element="CustomerDetailsResponse" rowName="CustomerDetail"> <element name="Rating" column="RATING_C" xsdType="xs:double" /> <element name="MaximumAmount" column="MAX_AMOUNT_C" xsdType="xs:double" /> </result> <param name="name" paramType="SCALAR" sqlType="STRING" type="IN" /> </query> <operation name="GetCustomerDetails"> <description></description> <call-query href="customerSelectQuery"> <with-param name="name" query-param="name" /> </call-query> </operation> </data>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing"> <wsa:To>http://localhost:8082/services/CustomerDetailService.SOAP11Endpoint/</wsa:To> <wsa:MessageID>urn:uuid:9a9316ae-adf2-490d-978b-0805b6010cfe</wsa:MessageID> <wsa:Action>urn:GetCustomerDetails</wsa:Action> </soapenv:Header> <soapenv:Body> <GetCustomerDetails xmlns="http://ws.wso2.org/dataservice"> <ns2:name xmlns:ns2="http://ws.wso2.org/dataservice">CustomerA</ns2:name> </GetCustomerDetails> </soapenv:Body> </soapenv:Envelope>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing"> <wsa:Action>urn:GetCustomerDetailsResponse</wsa:Action> <wsa:RelatesTo>urn:uuid:9a9316ae-adf2-490d-978b-0805b6010cfe</wsa:RelatesTo> </soapenv:Header> <soapenv:Body> <CustomerDetailsResponse xmlns="http://ws.wso2.org/dataservice"> <CustomerDetail> <Rating>0.6</Rating> <MaximumAmount>350.0</MaximumAmount> </CustomerDetail> </CustomerDetailsResponse> </soapenv:Body> </soapenv:Envelope>
WSO2 BPS process artifact mainly consist of a BPEL process file, Apache ODE specific deployment descriptor file, a set of WSDL files used in the partner links and other related files such as XSLT. All these files are deployed as a zip file.
Customer Order BPEL file
<process name="CustomerOrderService" targetNamespace="http://wso2.org/sample/rule" xmlns="http://docs.oasis-open.org/wsbpel/2.0/process/executable" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:bpel="http://docs.oasis-open.org/wsbpel/2.0/process/executable" xmlns:ns1="http://pojo.rule.sample/xsd" xmlns:ns2="http://ws.wso2.org/dataservice" xmlns:ns0="http://wso2.org/sample/rule"> <import namespace="http://wso2.org/sample/rule" location="CustomerOrderService.wsdl" importType="http://schemas.xmlsoap.org/wsdl/"/> <import namespace="http://wso2.org/sample/rule" location="PriceCalculatorService.wsdl" importType="http://schemas.xmlsoap.org/wsdl/"/> <import namespace="http://wso2.org/sample/rule" location="OrderSelectorService.wsdl" importType="http://schemas.xmlsoap.org/wsdl/"/> <import namespace="http://ws.wso2.org/dataservice" location="CustomerDetailService.wsdl" importType="http://schemas.xmlsoap.org/wsdl/"/> <partnerLinks> <partnerLink name="CustomerOrderServicePL" partnerLinkType="ns0:CustomerOrderServicePLT" myRole="CustomerOrderServicePLTRole"/> <partnerLink name="PriceCalculatorServicePL" partnerLinkType="ns0:PriceCalculatorServicePLT" partnerRole="PriceCalculatorServicePLTRole"/> <partnerLink name="OrderSelectorServicePL" partnerLinkType="ns0:OrderSelectorServicePLT" partnerRole="OrderSelectorServicePLTRole"/> <partnerLink name="CustomerDetailServicePL" partnerLinkType="ns2:CustomerDetailServicePLT" partnerRole="CustomerDetailServicePLTRole"/> </partnerLinks> <variables> <variable name="customerOrderRequest" messageType="ns0:customerOrderRequest"/> <variable name="customerOrderResponse" messageType="ns0:customerOrderResponse"/> <variable name="calculatePriceRequest" messageType="ns0:CalculatePriceRequest"/> <variable name="calculatePriceResponse" messageType="ns0:CalculatePriceResponse"/> <variable name="getCustomerDetailsRequest" messageType="ns2:GetCustomerDetailsRequest"/> <variable name="getCustomerDetailsResponse" messageType="ns2:GetCustomerDetailsResponse"/> <variable name="selectOrderRequest" messageType="ns0:selectOrderRequest"/> <variable name="selectOrderResponse" messageType="ns0:selectOrderResponse"/> <variable name="customerName" type="xsd:string"/> <variable name="maximumAmount" type="xsd:double"/> <variable name="rating" type="xsd:double"/> </variables> <sequence> <receive name="receiveCustomerOrderRequest" createInstance="yes" partnerLink="CustomerOrderServicePL" operation="customerOrder" portType="ns0:CustomerOrderServicePortType" variable="customerOrderRequest"> </receive> <assign name="AssignToCalulatePriceRequest"> <copy> <from> <![CDATA[bpel:doXslTransform('CustemerOrderInputTransfer.xslt', $customerOrderRequest.parameters)]]> </from> <to>$calculatePriceRequest.parameters</to> </copy> </assign> <invoke name="InvokePriceCalculatorService" partnerLink="PriceCalculatorServicePL" operation="CalculatePrice" portType="ns0:PriceCalculatorServicePortType" inputVariable="calculatePriceRequest" outputVariable="calculatePriceResponse" /> <assign name="CustomerDetailsRequest"> <copy> <from> <literal> <ns2:GetCustomerDetails> <ns2:name></ns2:name> </ns2:GetCustomerDetails> </literal> </from> <to>$getCustomerDetailsRequest.parameters</to> </copy> <copy> <from>$customerOrderRequest.parameters/ns0:Customer/ns1:name/text()</from> <to>$customerName</to> </copy> <copy> <from>$customerName</from> <to>$getCustomerDetailsRequest.parameters/ns2:name</to> </copy> </assign> <invoke name="InvokeCustomerDetailService" partnerLink="CustomerDetailServicePL" operation="GetCustomerDetails" portType="ns2:CustomerDetailServicePortType" inputVariable="getCustomerDetailsRequest" outputVariable="getCustomerDetailsResponse" /> <assign name="AssignToOrderSelectorServiceRequest"> <copy> <from>$getCustomerDetailsResponse.parameters/ns2:CustomerDetail/ns2:Rating/text()</from> <to>$rating</to> </copy> <copy> <from>$getCustomerDetailsResponse.parameters/ns2:CustomerDetail/ns2:MaximumAmount/text()</from> <to>$maximumAmount</to> </copy> <copy> <from> <![CDATA[bpel:doXslTransform('OrderSelectorInputTransfer.xslt', $calculatePriceResponse.parameters, "maxAmount", $maximumAmount, "name", $customerName,"rating",$rating)]]> </from> <to>$selectOrderRequest.parameters</to> </copy> </assign> <invoke name="InvokeOrderSelectorService" partnerLink="OrderSelectorServicePL" operation="selectOrder" portType="ns0:OrderSelectorServicePortType" inputVariable="selectOrderRequest" outputVariable="selectOrderResponse" /> <assign> <copy> <from> <literal> <ns0:customerOrderResponse></ns0:customerOrderResponse> </literal> </from> <to>$customerOrderResponse.parameters</to> </copy> <copy> <from>$selectOrderResponse.parameters</from> <to>$customerOrderResponse.parameters</to> </copy> </assign> <reply name="CalculatePriceResponse" partnerLink="CustomerOrderServicePL" operation="customerOrder" portType="ns0:CustomerOrderServicePortType" variable="customerOrderResponse"/> </sequence> </process>
Like in any BPEL file it starts with the process element which declares the name of this process and the name spaces to be used. As shown in the diagram CustomerOrderService integrate three web services and expose itself as a web service. Therefore first it imports all these WSDLs to this process. In a BPEL process communication between the external services and the process happens through partner links. Each partner link should have a name and a type which is defined in the WSDL of the corresponding service. This process has four partner links named 'CustomerOrderServicePL', 'PriceCalculatorServicePL', 'OrderSelectorServicePL' and 'CustomerDetailServicePL'. For an example it has the following partner link type in the 'PriceCalculatorService' for 'PriceCalculatorServicePL'.
<plnk:partnerLinkType name="PriceCalculatorServicePLT"> <plnk:role name="PriceCalculatorServicePLTRole" portType="brs:PriceCalculatorServicePortType"/> </plnk:partnerLinkType>
This BPEL process manipulates different request and response messages in order to build the final response. Within a BPEL sequence, variables with different types can be used to read input and output message and use various operations to process them. Variables section is used to declare such variables used in this sequence. Web service request and response messages can be read using variables with the corresponding message types of the services. Other variables can be declared either using xsd types or elements. Variables can also be used to keep the state of the business process as well.
The activity sequence is defined under the 'sequence' element. First it receives the 'CustomerOrderRequest' through the 'CustomerOrderServicePL' partner link. Then it transforms the received request using an XSLT file to invoke the 'PriceCalculatorService' service through the 'PriceCalculatorServicePL'. In order to invoke the 'OrderSelectorService' it should find the maximum allowed value for customer and the rating. To find these values, it uses the 'CustomerDetailService'. The request to invoke this service is built using the assign and copy functions available with the BPEL language. After that it invokes the 'OrderSelectorService' to find the selected order for the customer. Finally it creates the response message to client using the received response message. Unlink in the ESB configuration this whole process is defined using standard BPEL constructs so that it can run in any BPEL container with proper descriptor file.
<deploy xmlns="http://www.apache.org/ode/schemas/dd/2007/03" xmlns:ns1="http://wso2.org/sample/rule" xmlns:ns2="http://ws.wso2.org/dataservice"> <process name="ns1:CustomerOrderService"> <active>true</active> <provide partnerLink="CustomerOrderServicePL"> <service name="ns1:CustomerOrderService" port="CustomerOrderServiceHttpsSoap11Endpoint"/> </provide> <invoke partnerLink="PriceCalculatorServicePL"> <service name="ns1:PriceCalculatorService" port="PriceCalculatorServiceHttpSoap11Endpoint"/> </invoke> <invoke partnerLink="OrderSelectorServicePL"> <service name="ns1:OrderSelectorService" port="OrderSelectorServiceHttpSoap11Endpoint"/> </invoke> <invoke partnerLink="CustomerDetailServicePL"> <service name="ns2:CustomerDetailService" port="SOAP11Endpoint"/> </invoke> </process> </deploy>
WSO2 BPS is based on Apache ODE. Apache ODE uses a deployment descriptor file to specify the partner links, service wsdl and real port addresses for each and every process. The 'CustomerOrderService' provides one partner link to invoke itself and it will invoke three other services to get the required data. The port addresses from which it has to obtain data is given in the wsdl files itself.
Business decisions becoming more dynamic in nature. As a result of that, more developers tend to isolate the business logic in separate rule files. WSO2 BRS let users to expose the business logic written in such rule files as a web service so that any SOA system can consume it.
Within a SOA environment, services can be integrated either using an ESB or BPEL. WSO2 ESB and WSO2 BPS provide implementations of both. The integration language used in WSO2 ESB (which comes from Apache Synapse) is specific to Apache Synapse and it is not structured as a BPEL language. On the other hand, BPEL provides a standard and more structured language and WSDL based communication between different services. However an ESB process generally faster than a BPEL process due to transport level optimizations and the direct relationship with the language used and the internal synapse architecture.
Amila Suriarachchi, Software Architect, WSO2 Inc.