2012/12/05
5 Dec, 2012

Bringing Business Rules to SOA

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

Introduction

Today, most of the business organizations requires to change their business polices to suite the dynamic market requirements. Therefore these dynamic policy changes should appear in the underline software systems they use as well. Business rules is a widely used technique to fulfill such requirements. On the other hand most of the business organizations follows service oriented architectures (SOA) in order to provide both dynamic and inter operable systems. Hence such SOA systems required to use business rules in a seamless manner.

WSO2 Carbon platform provides a wide range of products to build software systems according to SOA principles. Jboss Drools is an open source library under Apache license which provides a business rules support to java programs. WSO2 BRS let users to publish such business rules written using drools language as web services. Further WSO2 ESB provides a rule mediator which can be used to process messages using business rules. Rest of this article explain the usage of rules in these two situations.

Applies To

WSO2 BRS 2.0.0
WSO2 ESB 4.1.0

Contents

Rules as a web service

Any rule based system, has a set of POJO objects called facts of which rules are being applied. These facts are stored in working memory and the rule engine, changes the state of these facts according to the given rules. Therefore if we consider initial set of facts as the inputs and facts in the working memory after applying the rules as output, a rule based service can be viewed as a one takes a set of facts as inputs and produces a set of facts as out put.

Rules

On the other hand a web service can be considered as an end point which takes an xml message as a request and outputs an xml message as a response. Therefore it is possible to implement a rule based web service by using xml messages to send and receive fact data. Such a web service can create the fact objects using request xml message and convert the out put facts to an xml as given below.

Rule Service Overview

Lets see how this concept has implemented with WSO2 BRS server. We can use an archive format to deploy these rule services using a custom deployer. This rule archive file can contain the business rules, facts classes and rule service descriptor (services.rsl) which describes details about the rule service. Hence a rule service descriptor file contains details about the rule set, input facts and output facts. Then the WSO2 BRS server can read these details and create the web service wsdl according to the given input and output facts.

Here is a simple use case where it uses business rules to calculate the price of an order. The descriptor file looks like this.

<brs:ruleService xmlns:brs="https://wso2.org/carbon/rules" name="PriceCalculatorService"
                 targetNamespace="https://wso2.org/carbon/rules" scope="request">
    <brs:ruleSet>
        <brs:properties/>
        <brs:rule resourceType="regular" sourceType="inline"><![CDATA[

        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();
                $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();
                $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();
                $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

    ]]></brs:rule>
    </brs:ruleSet>
    <brs:operation name="calculatePrice">
        <brs:input wrapperElementName="RequestOrder">
            <brs:fact type="sample.rule.pojo.Order"/>
        </brs:input>
        <brs:output wrapperElementName="ResponseOrder">
            <brs:fact type="sample.rule.pojo.Order"/>
        </brs:output>
    </brs:operation>
</brs:ruleService>

Rule service descriptor mainly consists of a rule set which is going to apply on the facts and a set of operations. Rule set can have many rules and a rule can be given as inline, as a file, as a registry key or a url. An operation has a set of input facts and out put facts. Inputs are the facts that will send by the rule service client and out put are the facts that client receives. Rule service automatically generates the request and response xml message formats based on the facts. Optionally users can define the wrapping element names and namespaces.

Rules as a mediator

WSO2 ESB(based on Apache Synapse) is a mediation framework which provides different mediation components to build the message processing sequence. Rules can also be considered as a possible mediator which takes an xml message as input and produces a processed xml message. The result xml message can be used as the new soap body message or use the information to route the message or do any other processing.

Rule Service Overview

Above diagram shows the configuration of the rule mediator. Rule mediator can obtain the input xml message from different sources. These sources can be soapBody, soapHeder or a property. Then the real input message to create input facts can be part of this message. Therefore users can obtain that part using an xpath. After that even when creating fact objects it is possible to specify an xpath to obtain the xml message path for that fact type.

For result facts, the resultXpath variable can be used to obtain the part of the generated result xml message to be added to the target. Again target can either be a soapBody, soapHeader or a property. And when adding result xml to target, again the location to be added can be specified using an xpath. Finally action can either be child, sibling or replace.

Following esb sequence shows how the rule mediator can be used both as a transformer and a mechanism to route the messages based on rules.

<proxy xmlns="http://ws.apache.org/ns/synapse" name="PriceCalculatorService" transports="https http" startOnLoad="true"
       trace="disable">
    <description/>
    <target>
        <inSequence>
            <brs:rule xmlns:brs="https://wso2.org/carbon/rules">
                <brs:source xpath="">soapBody</brs:source>
                <brs:target xpath="" resultXpath="" action="replace">soapBody</brs:target>
                <brs:ruleSet>
                    <brs:properties/>
                    <brs:rule resourceType="regular" sourceType="inline">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();
                        $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();
                        $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();
                        $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
                    </brs:rule>
                </brs:ruleSet>
                <brs:input>
                    <brs:fact xmlns:m1="https://wso2.org/carbon/rules" type="sample.rule.pojo.Order"
                              xpath="//m1:RequestOrder/m1:order"/>
                </brs:input>
                <brs:output namespace="https://pojo.rule.sample" wrapperElementName="RequestOrder">
                    <brs:fact type="sample.rule.pojo.Order"/>
                </brs:output>
            </brs:rule>
            <log level="full"/>
            <brs:rule xmlns:brs="https://wso2.org/carbon/rules">
                <brs:source xpath="">soapBody</brs:source>
                <brs:target xmlns:m1="https://pojo.rule.sample" xpath="" resultXpath="//m1:order/m1:priority/text()"
                            action="replace">$priority
                </brs:target>
                <brs:ruleSet>
                    <brs:properties/>
                    <brs:rule resourceType="regular" sourceType="inline">package sample.rule;

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

                        rule "if price is greater than 250 and rating is greater than 0.6 priority is set to 1" dialect
                        "mvel" no-loop true

                        when
                        $order : Order(price > 250, customer.rating > 0.6);
                        then
                        $order.setPriority("priority1");
                        end

                        rule "if price is greater than 250 and rating is less than or equal 0.6 priority is set to 2"
                        dialect "mvel" no-loop true

                        when
                        $order : Order(price > 250, customer.rating <= 0.6);
                        then
                        $order.setPriority("priority2");
                        end

                        rule "if price is less than 250" dialect "mvel" no-loop true

                        when
                        $order : Order(price <= 250);
                        then
                        $order.setPriority("priority3");
                        end
                    </brs:rule>
                </brs:ruleSet>
                <brs:input>
                    <brs:fact xmlns:m1="https://pojo.rule.sample" type="sample.rule.pojo.Order"
                              xpath="//m1:RequestOrder/m1:order"/>
                </brs:input>
                <brs:output namespace="https://pojo.rule.sample" wrapperElementName="RequestOrder">
                    <brs:fact type="sample.rule.pojo.Order"/>
                </brs:output>
            </brs:rule>
            <switch source="get-property('priority')">
                <case regex="priority1">
                    <log level="custom">
                        <property name="Priority 1 message" value="received"/>
                    </log>
                </case>
                <case regex="priority2">
                    <log level="custom">
                        <property name="priority 2 message" value="received"/>
                    </log>
                </case>
                <case regex="priority3">
                    <log level="custom">
                        <property name="Priority 3 message" value="received"/>
                    </log>
                </case>
            </switch>
        </inSequence>
    </target>
</proxy>

In the first mediator it takes the soapBody of the current ESB sequence message as the input and use the xpath given when creating the Order fact object from that. Then simply replace the soapBody of the current ESB message using the result with the calculated price. In other words it has transformed the xml message on the initial soap message to another xml message. Therefore this is a way of applying rules to an xml message.

In the second mediator again it gets the soapBody as the input message to generate facts. Then the rule set the priority of the message accordingly. In the result xml, it uses the resultXpath to get the priority part of the message. And after that it places the result string in a property. Finally ESB switch mediator can use this property to route the message to the correct end point. This way we can use business rules to implement some routing decisions.

Abstract Architecture

Rule Design

As shown in the above, both Rule service and the mediator mainly convert an xml document into set of facts, apply rules and convert result facts back to xml. Therefore we can reuse that functionality within both components, while having specific functionalities to implement web service and mediator parts. Following diagram shows the abstract architecture of the rule component.

The rule backend runtime interface is used to abstract out the real rule implementations from the kernel. Currently it has two implementations using drools and JSR94 specification. The rule kernel component provides the data binding functionality. The RuleEngine interface takes an xml message with the relevant descriptor objects and returns the result xml message. Within the RuleEngine it convert the xml message to POJO objects invoke the rules using backend runtime interface and convert result object to xml again.

The web service component provides the web service functionalities. First it uses the RuleDeployer to deploy the rule service. Then use the RuleMessageReceiver to receive messages from the axis2 kernel and inject them to RuleEngine. Apart from processing xml message a web service suppose to provide a wsdl which describes the service. Schema generator is used to create the schemas for the facts and axis2 wsdl generator is used to create the wsdl for the service.

The rule mediator component is used to obtain the input xml message from the synapse sequence source location and set the response message back to the target location.

Conclusion

Business rules are widely being used as a solution to the dynamic business environments. WSO2 BRS server and WSO2 ESB server provides components that enable usage of rules as a web service and as a mediation component.

Author

Amila Suriarachchi, Software Architect, WSO2 Inc.

 

About Author

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