2011/05/05
5 May, 2011

Integrate Rules with SOA

  • Amila Suriarachchi
  • Architect, Member, Management Committee - Data Technologies - WSO2

 

 

Introduction

Businesses use different types of systems to manage their operations. However, since customers, partners, etc. also interact with these systems, they should also contain key business decisions. But these business decisions are always subjected to change and hence should be maintained within the 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; which makes them readable, even by business analysts.

Processing customer order requests heavily utilizes business logic. Therefore this article describes a sample SOA system which uses the WSO2 Enterprise Service Bus (ESB) as the integration layer while using the WSO2 Business Rules Server (BRS) to expose business rules, written in Drools, as web services. This sample uses an integrated service called CustomerOrderService which gives the preferred order out of a set of orders given by the customer.

The Scenario

 

First CustomerOrderService receives the request message which consists of a customer details and their set of orders. 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 item, CustomerOrderService queries the Customer table 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. Sample code which has been tested with WSO2 BRS 1.1.0 and WSO2 ESB 3.0.0 can be found here

PriceCalculatorService

PriceCalculatorService is used to calculate the order price for the given item list. It sets the various discount amounts depending on the number of items ordered. This sample uses the following business rules written using Drools.

package sample.rule;

import sample.rule.pojo.Item;
import sample.rule.pojo.Order;

rule "Items with less than 2 discount 5%" dialect "mvel" no-loop true

    when
        $order : Order($orderID : orderID);
        $item : Item($type : type) from $order.items;
        $count : Long(longValue < 2) from accumulate(Item(type == $type) from $order.items, count());
    then
	$order.setPrice($order.price + $item.price * 0.95);
    end

rule "Items between 2 and 5 discount 8%" dialect "mvel" no-loop true

    when
        $order : Order($orderID : orderID);
        $item : Item($type : type) from $order.items;
        $count : Long(eval(longValue >= 2 and longValue < 5)) from accumulate(Item(type == $type) from $order.items, count());
    then
        $order.setPrice($order.price + $item.price * 0.92);
    end

rule "Items greater than 5 discount 10%" dialect "mvel" no-loop true

    when
        $order : Order($orderID : orderID);
        $item : Item($type : type) from $order.items;
        $count : Long(longValue >= 5) from accumulate(Item(type == $type) from $order.items, count());
    then
        $order.setPrice($order.price + $item.price * 0.90);
    end

There are three rules used to calculate and set the order price. These rules are applied to a set of order objects which contains items in the working memory. The first rule adds 5% discount if there no more than two items from a particular item type. The second rule adds 8% discount if the number of items of a particular type is between 2 and 5. The third rule adds 10% discount if there are more than 5 items from a particular type. The following XML messages shows sample request and response messages for this business decision:

Request message

<soapenv:Envelope xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope">
      <soapenv:Body>
         <ns2:CalculatePriceRequest xmlns:ns2="https://wso2.org/sample/rule" xmlns:ns1="https://pojo.rule.sample/xsd">
            <ns2:Order>
               <ns1:items>
                  <ns1:price>50.0</ns1:price>
                  <ns1:type>TypeA</ns1:type>
               </ns1:items>
               <ns1:items>
                  <ns1:price>60.0</ns1:price>
                  <ns1:type>TypeB</ns1:type>
               </ns1:items>
               <ns1:items>
                  <ns1:price>70.0</ns1:price>
                  <ns1:type>TypeC</ns1:type>
               </ns1:items>
               <ns1:items>
                  <ns1:price>80.0</ns1:price>
                  <ns1:type>TypeA</ns1:type>
               </ns1:items>
               <ns1:items>
                  <ns1:price>80.0</ns1:price>
                  <ns1:type>TypeA</ns1:type>
               </ns1:items>
               <ns1:orderID>Order1</ns1:orderID>
            </ns2:Order>
            <ns2:Order>
               <ns1:items>
                  <ns1:price>50.0</ns1:price>
                  <ns1:type>TypeA</ns1:type>
               </ns1:items>
               <ns1:items>
                  <ns1:price>60.0</ns1:price>
                  <ns1:type>TypeA</ns1:type>
               </ns1:items>
               <ns1:items>
                  <ns1:price>70.0</ns1:price>
                  <ns1:type>TypeC</ns1:type>
               </ns1:items>
               <ns1:items>
                  <ns1:price>80.0</ns1:price>
                  <ns1:type>TypeA</ns1:type>
               </ns1:items>
               <ns1:items>
                  <ns1:price>80.0</ns1:price>
                  <ns1:type>TypeA</ns1:type>
               </ns1:items>
               <ns1:orderID>Order2</ns1:orderID>
            </ns2:Order>
         </ns2:CalculatePriceRequest>
      </soapenv:Body>
   </soapenv:Envelope>

Response message

<soapenv:Envelope xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope">
      <soapenv:Body>
         <result>
            <brs:Order xmlns:brs="https://wso2.org/sample/rule" xmlns:ax2211="https://pojo.rule.sample/xsd" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:type="ax2211:Order">
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>50.0</ax2211:price>
                  <ax2211:type>TypeA</ax2211:type>
               </ax2211:items>
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>60.0</ax2211:price>
                  <ax2211:type>TypeB</ax2211:type>
               </ax2211:items>
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>70.0</ax2211:price>
                  <ax2211:type>TypeC</ax2211:type>
               </ax2211:items>
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>80.0</ax2211:price>
                  <ax2211:type>TypeA</ax2211:type>
               </ax2211:items>
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>80.0</ax2211:price>
                  <ax2211:type>TypeA</ax2211:type>
               </ax2211:items>
               <ax2211:orderID>Order1</ax2211:orderID>
               <ax2211:price>316.70000000000005</ax2211:price>
            </brs:Order>
            <brs:Order xmlns:brs="https://wso2.org/sample/rule" xmlns:ax2211="https://pojo.rule.sample/xsd" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:type="ax2211:Order">
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>50.0</ax2211:price>
                  <ax2211:type>TypeA</ax2211:type>
               </ax2211:items>
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>60.0</ax2211:price>
                  <ax2211:type>TypeA</ax2211:type>
               </ax2211:items>
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>70.0</ax2211:price>
                  <ax2211:type>TypeC</ax2211:type>
               </ax2211:items>
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>80.0</ax2211:price>
                  <ax2211:type>TypeA</ax2211:type>
               </ax2211:items>
               <ax2211:items xsi:type="ax2211:Item">
                  <ax2211:price>80.0</ax2211:price>
                  <ax2211:type>TypeA</ax2211:type>
               </ax2211:items>
               <ax2211:orderID>Order2</ax2211:orderID>
               <ax2211:price>314.90000000000003</ax2211:price>
            </brs:Order>
         </result>
      </soapenv:Body>
   </soapenv:Envelope>

OrderSelectorService

The OrderSelectorService selects the order with the maximum price, but below the maximum allowed limit for the customer. If a customer has a rating higher than 0.5, a 20% discount is added. The following Drool file can be used for this rule:

package sample.rule;

import sample.rule.pojo.*;

rule "Select the order with the maximum value when rating < 0.5" dialect "mvel" no-loop true

when
    $customer : Customer(rating < 0.5);
    $maxValue : Double() from accumulate (Order(price < $customer.maxAmount, $price : price) from $customer.orders, max($price));
    $order : Order(price == $maxValue) from $customer.orders;
then
    $customer.setSelectedOrder($order);
    $customer.setPrice($order.price);
end

rule "Select the order with the maximum value when rating > 0.5" dialect "mvel" no-loop true

when
    $customer : Customer(rating >= 0.5);
    $maxValue : Double() from accumulate (Order(eval(price * 0.8 < $customer.maxAmount), $price : price) from $customer.orders, max($price));
    $order : Order(price == $maxValue) from $customer.orders;
then
    $customer.setSelectedOrder($order);
    $customer.setPrice($order.price);
end

Request Message

<soapenv:Envelope xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope">
    <soapenv:Body>
        <ns2:selectOrderRequest xmlns:ns2="https://wso2.org/sample/rule"
                                xmlns:ns1="https://pojo.rule.sample/xsd">
            <ns2:Customer>
                <ns1:maxAmount>350.0</ns1:maxAmount>
                <ns1:name>CustomerA</ns1:name>
                <ns1:orders>
                    <ns1:orderID>Order1</ns1:orderID>
                    <ns1:price>316.70000000000005</ns1:price>
                </ns1:orders>
                <ns1:orders>
                    <ns1:orderID>Order2</ns1:orderID>
                    <ns1:price>314.90000000000003</ns1:price>
                </ns1:orders>
                <ns1:rating>0.6</ns1:rating>
            </ns2:Customer>
        </ns2:selectOrderRequest>
    </soapenv:Body>
</soapenv:Envelope>

Response Message

<soapenv:Envelope xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope">
    <soapenv:Body>
        <result>
            <brs:Customer xmlns:brs="https://wso2.org/sample/rule"
                          xmlns:ax22="https://pojo.rule.sample/xsd"
                          xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
                          xsi:type="ax22:Customer">
                <ax22:maxAmount>350.0</ax22:maxAmount>
                <ax22:name>CustomerA</ax22:name>
                <ax22:orders xsi:type="ax22:Order">
                    <ax22:items xsi:nil="true"/>
                    <ax22:orderID>Order1</ax22:orderID>
                    <ax22:price>316.70000000000005</ax22:price>
                </ax22:orders>
                <ax22:orders xsi:type="ax22:Order">
                    <ax22:items xsi:nil="true"/>
                    <ax22:orderID>Order2</ax22:orderID>
                    <ax22:price>314.90000000000003</ax22:price>
                </ax22:orders>
                <ax22:price>316.70000000000005</ax22:price>
                <ax22:rating>0.6</ax22:rating>
                <ax22:selectedOrder xsi:type="ax22:Order">
                    <ax22:items xsi:nil="true"/>
                    <ax22:orderID>Order1</ax22:orderID>
                    <ax22:price>316.70000000000005</ax22:price>
                </ax22:selectedOrder>
            </brs:Customer>
        </result>
    </soapenv:Body>
</soapenv:Envelope>

CustomerOrderService

CustomerOrderService, which is implemented as an ESB proxy service, does the integration part. This proxy service gets a customer with a set of orders and returns the selected order. It processes the received customer order request with the following proxy service configuration:

Proxy service configuration

<proxy name="CustomerOrderService" transports="https http" startOnLoad="true" trace="disable">
    <target inSequence="CustomerOrderInSequence" outSequence="CustomerOrderOutSequence"/>
    <publishWSDL key="conf:/wsdl/proxy_service.wsdl"/>
</proxy>
<sequence name="CustomerOrderInSequence">
    <property xmlns:ns2="https://org.apache.synapse/xsd" xmlns:ns="https://org.apache.synapse/xsd"
              xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope"
              xmlns:ns5="https://wso2.org/sample/rule" xmlns:ns6="https://pojo.rule.sample/xsd"
              name="name" expression="//ns5:customerOrderRequest/ns5:Customer/ns6:name/text()"
              scope="default"/>
    <xslt key="conf:/xslt/CustemerOrderInputTransfer.xslt"/>
    <property name="nextSequence" value="OrderSelectorInSequence" scope="default"
              type="STRING"/>
    <send>
        <endpoint name="endpoint_urn_uuid_ABF09DE95F6514C1AF15728013415852-56580331">
            <address uri="https://localhost:9763/services/PriceCalculatorService"/>
        </endpoint>
    </send>
</sequence>
<sequence name="CustomerOrderOutSequence">
    <switch xmlns:ns2="https://org.apache.synapse/xsd" xmlns:ns="https://org.apache.synapse/xsd"
            source="get-property('nextSequence')">
        <case regex="OrderSelectorInSequence">
            <dblookup>
                <connection>
                    <pool>
                        <password>stratos</password>
                        <user>stratos</user>
                        <url>jdbc:mysql://localhost:3306/DATASERVICE_SAMPLE</url>
                        <driver>com.mysql.jdbc.Driver</driver>
                    </pool>
                </connection>
                <statement>
                    <sql>
                        select RATING_C,MAX_AMOUNT_C from CUSTOMER_T where NAME_C=?
                    </sql>
                    <parameter expression="get-property('name')" type="VARCHAR"/>
                    <result name="maxAmount" column="MAX_AMOUNT_C"/>
                    <result name="rating" column="RATING_C"/>
                </statement>
            </dblookup>
            <xslt key="conf:/xslt/OrderSelectorInputTransfer.xslt">
                <property name="maxAmount" expression="get-property('maxAmount')"/>
                <property name="name" expression="get-property('name')"/>
                <property name="rating" expression="get-property('rating')"/>
            </xslt>
            <property name="nextSequence" value="finish" scope="default" type="STRING"/>
            <send>
                <endpoint name="endpoint_urn_uuid_BEE1E79674469465A930760264687891786100296">
                    <address uri="https://localhost:9763/services/OrderSelectorService/"/>
                </endpoint>
            </send>
        </case>
        <default>
            <enrich>
                <source xmlns:brs="https://wso2.org/sample/rule" clone="true"
                        xpath="//result/brs:Customer"/>
                <target type="property" property="CustomerElement"/>
            </enrich>
            <enrich>
                <source type="inline" clone="true">
                    <brs:customerOrderResponse xmlns:brs="https://wso2.org/sample/rule"/>
                </source>
                <target type="body"/>
            </enrich>
            <enrich>
                <source type="property" property="CustomerElement"/>
                <target type="body" action="child"/>
            </enrich>
            <send/>
        </default>
    </switch>
    <drop/>
</sequence>

First, it transforms the received XML request to the PriceCalculatorService input request type. The WSO2 ESB does the service integration by processing the response message by keeping the state of the next sequence to be processed as a property. Therefore, CustomerOrderInSequence keeps the OrderSelectorInSequence as the next sequence. As the last step, it sends the transformed message to the PriceCalculatorService end point.

After processing the Orders at the PriceCalculatorService, it receives the response at the CustomerOrderOutSequence. Here, this proxy service uses the switch mediator to determine the next sequence to be executed according to the nextSequence property saved. Once the response with order prices is received, it has to invoke the OrderSelectorService. First, it finds the maximum amount and the rating using a database look up. Then, it creates the request message for the OrderSelectorService again using an XSLT transformation with the above obtained values. Finally, it sets the nextSequnce value as Finish.

OrderSelectorService selects the order according to the rules and sends the response. After getting this response, the ESB proxy service executes the default sequence of the switch mediator where it transform the response message into a format required by the client using the Enrich mediator.

Request Message

<soapenv:Envelope xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope">
    <soapenv:Body>
        <ns2:customerOrderRequest xmlns:ns2="https://wso2.org/sample/rule">
            <ns2:Customer>
                <ns1:name xmlns:ns1="https://pojo.rule.sample/xsd">CustomerA</ns1:name>
                <orders xmlns="https://pojo.rule.sample/xsd">
                    <items>
                        <price>50.0</price>
                        <type>TypeA</type>
                    </items>
                    <items>
                        <price>60.0</price>
                        <type>TypeB</type>
                    </items>
                    <items>
                        <price>70.0</price>
                        <type>TypeC</type>
                    </items>
                    <items>
                        <price>80.0</price>
                        <type>TypeA</type>
                    </items>
                    <items>
                        <price>80.0</price>
                        <type>TypeA</type>
                    </items>
                    <orderID>Order1</orderID>
                </orders>
                <orders xmlns="https://pojo.rule.sample/xsd">
                    <items>
                        <price>50.0</price>
                        <type>TypeA</type>
                    </items>
                    <items>
                        <price>60.0</price>
                        <type>TypeA</type>
                    </items>
                    <items>
                        <price>70.0</price>
                        <type>TypeC</type>
                    </items>
                    <items>
                        <price>80.0</price>
                        <type>TypeA</type>
                    </items>
                    <items>
                        <price>80.0</price>
                        <type>TypeA</type>
                    </items>
                    <orderID>Order2</orderID>
                </orders>
            </ns2:Customer>
        </ns2:customerOrderRequest>
    </soapenv:Body>
</soapenv:Envelope>

Response Message

<soapenv:Envelope xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope">
    <soapenv:Body>
        <brs:customerOrderResponse xmlns:brs="https://wso2.org/sample/rule">
            <brs:Customer xmlns:ax22="https://pojo.rule.sample/xsd"
                          xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
                          xsi:type="ax22:Customer">
                <ax22:maxAmount>350.0</ax22:maxAmount>
                <ax22:name>CustomerA</ax22:name>
                <ax22:orders xsi:type="ax22:Order">
                    <ax22:items xsi:nil="true"/>
                    <ax22:orderID>Order1</ax22:orderID>
                    <ax22:price>316.70000000000005</ax22:price>
                </ax22:orders>
                <ax22:orders xsi:type="ax22:Order">
                    <ax22:items xsi:nil="true"/>
                    <ax22:orderID>Order2</ax22:orderID>
                    <ax22:price>314.90000000000003</ax22:price>
                </ax22:orders>
                <ax22:price>316.70000000000005</ax22:price>
                <ax22:rating>0.6</ax22:rating>
                <ax22:selectedOrder xsi:type="ax22:Order">
                    <ax22:items xsi:nil="true"/>
                    <ax22:orderID>Order1</ax22:orderID>
                    <ax22:price>316.70000000000005</ax22:price>
                </ax22:selectedOrder>
            </brs:Customer>
        </brs:customerOrderResponse>
    </soapenv:Body>
</soapenv:Envelope>

Conclusion

Business decisions are becoming more dynamic in nature. As a result, more developers tend to isolate business logic in separate rule files. The WSO2 BRS lets users expose the business logic written in such rule files as a web service so that any SOA system can consume it. This article clearly demonstrate how such an integration can be done with the WSO2 ESB.

Author

Amila Suriarachchi

Software Architect; WSO2, Inc.

[email protected]

 

About Author

  • Amila Suriarachchi
  • Architect, Member, Management Committee - Data Technologies
  • WSO2 Inc.