Friday, April 6, 2012

Implementing a Domain Specific Language

I recently read a great research article titled "Cloud Process Execution Engine - Evaluation of the Core Concepts" by Juergen Mangler, Gerhard Stuermer, and Erich Schikuta from the University of Vienna - Faculty of Computer Science - Workflow Systems and Technologies Group.  You can download a PDF version here.  The article discusses a very elegant workflow execution language named WEE that can be used for business process orchestration.  Instead of using an XML-based workflow language like BPEL, WWE uses a very simple set of statements including:
  • Activity - used to execute a specific task like manipulating a variable or calling an external service
  • Parallel - defines two or more parallel branches that can be executed concurrently.  
  • Choose - implements control flow similar to if/otherwise type of logic
  • Cycle - provides support for looping
  • Endpoint - defines an external service
  • Context - performs variable definition and initialization
A simple workflow example that for making an airline and hotel reservation with an optional notification is shown below.

endpoint :epAirBook => uri:airbooking
endpoint :epHotelBook => uri:hotelbooking
endpoint :epAirPay => uri:airpayment
endpoint :epHotelPay => uri:hotelpayment
endpoint :epInform => uri:companyinform

context :persons => 3
context :creditcard => 'Visa12345'

context :airline => nil
context :hotel => nil
context :from => 'Vienna'
context :to => 'Prag'
context :sum => 0

controlflow do
  
    cycle (@persons > 0) do

        % book an airline flight
        activity :bookFlight, :call, epAirBook, @from, @to do |id|
            @airline = id
        end
      
        % pay for the flight
        activity :payFlight, :call, epAirPay, @airline, @creditcard do | amount |
            @sum += amount
        end
      
        % book a hotel
        activity :bookHotel, :call, epHotelBook, @to do |id|
            @hotel = id
        end
      
        % pay for the hotel
        activity :payHotel, :call, epHotelPay, @hotel, @creditcard do |amount|
            @sum += amount
        end

        % decrement the number of people
        activity :countdown, :manipulate do
            @persons += -1
        end
    end

    % alert if the total amount is greater than 5000
    choose do
        alternative (@sum > 5000) do
            activity :inform, :call, epInform, @sum
            end
        end
    end
end

As an experiment, I decided to try to implement this workflow language using the Erlang programming language because of it's built-in support for concurrent processes, multi-core processors, distributed clusters,  rock-solid performance, and scalability.  The availability of Erlang tools to define parsers (like LEX and YACC) was also of huge assistance.

Overall, it took me about a week to develop the parser and run-time engine but part of that time was taken with dusting off my Erlang programming skills that had gotten a bit rusty.  My appreciation for the simplicity and elegance of the WEE language steadily went up as I implemented each feature.  Compared to developing BPEL processes using cumbersome XML, WEE processes are much simpler to develop and maintain.

While I have made no attempts at optimization yet, the Erlang-based WEE run-time engine seems to perform very well.  I create a small workflow process that loops one million times and it executes in under 5 seconds on a laptop-class PC.

%
% Test performance of large loops
%
endpoint :timer => uri:display
context :counter => 1000000

controlflow do

    % call the timer endpoint to display the current time
    activity :starttime, :call, timer
    end

    % main loop
    cycle (@counter > 1) do
        activity :decrement, :manipulate do
            @counter += -1
        end
    end

    % call the timer endpoint to display the current time
    activity :endtime, :call, timer
    end
end

For fun, I built the equivalent process using a commercial BPEL engine and ran some timing tests on the same machine.  The source code is shown below.

Number of iterations      Erlang WEE        BPEL   
                    10,000        < 1 second        3 seconds
                  100,000        < 1 second        57 seconds
               1,000,000        5 seconds          Caused the server to crash after a few minutes

Having the code that I wrote being able to perform a million iterations in under 5 seconds made me very happy.

BPEL Source code:

<?xml version = "1.0" encoding = "UTF-8" ?>
<!--
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  Oracle JDeveloper BPEL Designer
 
  Created: Fri Apr 06 09:15:36 CDT 2012
  Type: BPEL 2.0 Process
  Purpose: Asynchronous BPEL Process
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-->
<process name="LoopProcess"
         targetNamespace="http://xmlns.oracle.com/Sandbox/LoopTest/LoopProcess"
         xmlns="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
         xmlns:client="http://xmlns.oracle.com/Sandbox/LoopTest/LoopProcess"
         xmlns:ora="http://schemas.oracle.com/xpath/extension"
         xmlns:bpelx="http://schemas.oracle.com/bpel/extension"
         xmlns:bpel="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
         xmlns:bpws="http://schemas.xmlsoap.org/ws/2003/03/business-process/"
         xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
         xmlns:oraext="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.ExtFunc"
         xmlns:dvm="http://www.oracle.com/XSL/Transform/java/oracle.tip.dvm.LookupValue"
         xmlns:hwf="http://xmlns.oracle.com/bpel/workflow/xpath"
         xmlns:ids="http://xmlns.oracle.com/bpel/services/IdentityService/xpath"
         xmlns:bpm="http://xmlns.oracle.com/bpmn20/extensions"
         xmlns:xdk="http://schemas.oracle.com/bpel/extension/xpath/function/xdk"
         xmlns:xref="http://www.oracle.com/XSL/Transform/java/oracle.tip.xref.xpath.XRefXPathFunctions"
         xmlns:ldap="http://schemas.oracle.com/xpath/extension/ldap">

    <import namespace="http://xmlns.oracle.com/Sandbox/LoopTest/LoopProcess" location="LoopProcess.wsdl" importType="http://schemas.xmlsoap.org/wsdl/"/>
    <!--
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        PARTNERLINKS                                                     
        List of services participating in this BPEL process              
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    -->
    <partnerLinks>
        <!--
      The 'client' role represents the requester of this service. It is
      used for callback. The location and correlation information associated
      with the client role are automatically set using WS-Addressing.
    -->
        <partnerLink name="loopprocess_client" partnerLinkType="client:LoopProcess" myRole="LoopProcessProvider" partnerRole="LoopProcessRequester"/>
    </partnerLinks>

    <!--
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        VARIABLES                                                       
        List of messages and XML documents used within this BPEL process
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    -->
    <variables>
        <!-- Reference to the message passed as input during initiation -->
        <variable name="inputVariable" messageType="client:LoopProcessRequestMessage"/>

        <!-- Reference to the message that will be sent back to the requester during callback -->
        <variable name="outputVariable" messageType="client:LoopProcessResponseMessage"/>
        <variable name="VariableCounter" type="xsd:long">
            <from>1000000</from>
        </variable>
    </variables>

    <!--
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
       ORCHESTRATION LOGIC                                              
       Set of activities coordinating the flow of messages across the   
       services integrated within this business process                 
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    -->
    <sequence name="main">
        <!-- Receive input from requestor. (Note: This maps to operation defined in LoopProcess.wsdl) -->
        <receive name="receiveInput" partnerLink="loopprocess_client" portType="client:LoopProcess" operation="process" variable="inputVariable" createInstance="yes"/>

        <!--
          Asynchronous callback to the requester. (Note: the callback location and correlation id is transparently handled using WS-addressing.)
        -->
        <repeatUntil name="RepeatUntil1">
            <assign name="AssignCounter">
                <copy>
                    <from>$VariableCounter - 1</from>
                    <to>$VariableCounter</to>
                </copy>
            </assign>
            <condition>$VariableCounter &lt; 1</condition>
        </repeatUntil>
        <invoke name="callbackClient" partnerLink="loopprocess_client" portType="client:LoopProcessCallback" operation="processResponse" inputVariable="outputVariable"/>
    </sequence>
</process>


No comments:

Post a Comment