Saturday, 15 May 2010

BPEL compensation handlers with Glassfish ESB example

Introduction
Recently I am involved in integration project based on Glassfish ESB and I needed to get into BPEL technology. There was one thing that really impressed me - compensation handlers.

Why do we need compensation handlers?
Imagine a simple book ordering process defined in BPEL and exposed as a webservice.
Process flow would be:
  • receive needed information: bookId, customerId, creditCardNumber
  • call BookOrderWS - webservice responsible for creating order
  • call PaymentWS - webservice responsible for payment
  • return result of the process
Execution of this process looks very simple until we consider that something may go wrong. Unpredicted exception may occur in the middle of the process. For example payment component may not be working and we will get exception while calling it. Without any fault handling our BPEL process will just stop after customer invoking it via WS will receive fault information. The worst thing is that earlier part of the process was executed properly - order was created by BookOrderWS. So, the book will be posted to customer even though he was not charged for it;)

Possible solutions:
  • use catch clause to react on errors on each webservice call
  • use componsation handlers - for each webservice call define a set of actions that will undo its execution
In first solution we could catch exception while calling PaymentWS and call another webservice that would cancel the order that was created. It would work in our case, but imagine a process that orchestrates ten or more webservice calls - code would be very complicated.

That's why I prefer second solution. No matter in which process step error occurs - all previous steps will know how to undo its operation. Process definition will be much clearer.

Implementation
Now I will show a draft of simple implementation that I prepared to test this concept. During development I was using Glassfish ESB 2.2 and bundled Netbeans IDE 6.7.1.

My application consists of following projects:
  • BookOrderWS
  • PaymentWS
  • BookOrderBPEL
  • BookOrderCA
Webservices
BookOrderWS is responsible for creating and canceling orders. It is implemented as EJB module and consists of one class:
package com.maciekm;

import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.ejb.Stateless;

@WebService()
@Stateless()
public class BookOrderWS {

@WebMethod(operationName = "orderBook")
public long orderBook(@WebParam(name = "bookId") long bookId,
@WebParam(name = "customerId") long customerId) {

long orderId = System.currentTimeMillis();
System.out.println("Order request for bookId: " + bookId + " from customerId: " + customerId + " - created orderId: " + orderId);
return orderId;
}

@WebMethod(operationName = "cancelOrder")
@Oneway
public void cancelOrder(@WebParam(name = "orderId") long orderId) {
System.out.println("Canceling orderId: "+orderId);
}
}
PaymentWS is also EJB module with very simple code:
package com.maciekm;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.ejb.Stateless;
import javax.jws.Oneway;

@WebService()
@Stateless()
public class PaymentWS {

@WebMethod(operationName = "payForOrder")
@Oneway
public void payForOrder(@WebParam(name = "orderId") long orderId,
@WebParam(name = "creditCardNumber") String creditCardNumber) {

System.out.println("Payment received for orderId: " + orderId + ", creditNumber:" + creditCardNumber);
if (creditCardNumber.equals("1234")) {
System.out.println("Payment error!");
throw new RuntimeException("some unexpected errors");
}
System.out.println("Payment completed!");
}
}
Here our mock implementation simulates payment processing. Unpredicted exception will occur for 1234 credit card number.

Process definition and composite application
WSDL of our process is based on very simple input and output schemas:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://xml.netbeans.org/schema/order"
xmlns:tns="http://xml.netbeans.org/schema/order"
elementFormDefault="qualified">
<xsd:element name="order" type="tns:order"/>
<xsd:complexType name="order">
<xsd:sequence>
<xsd:element name="bookId" type="xsd:integer"/>
<xsd:element name="customerId" type="xsd:integer"/>
<xsd:element name="creditCardNumber" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://xml.netbeans.org/schema/orderResult"
xmlns:tns="http://xml.netbeans.org/schema/orderResult"
elementFormDefault="qualified">
<xsd:element name="orderResult" type="tns:orderResult"/>
<xsd:complexType name="orderResult">
<xsd:sequence>
<xsd:element name="isCompleted" type="xsd:boolean"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
Process diagram:



Code:
<?xml version="1.0" encoding="UTF-8"?>
<process
name="bookOrderBPEL"
targetNamespace="http://enterprise.netbeans.org/bpel/BookOrderBPEL/bookOrderBPEL"
xmlns:tns="http://enterprise.netbeans.org/bpel/BookOrderBPEL/bookOrderBPEL"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
xmlns:sxt="http://www.sun.com/wsbpel/2.0/process/executable/SUNExtension/Trace"
xmlns:sxed="http://www.sun.com/wsbpel/2.0/process/executable/SUNExtension/Editor"
xmlns:sxeh="http://www.sun.com/wsbpel/2.0/process/executable/SUNExtension/ErrorHandling" xmlns:sxed2="http://www.sun.com/wsbpel/2.0/process/executable/SUNExtension/Editor2" xmlns:ns0="http://xml.netbeans.org/schema/order" xmlns:ns1="http://xml.netbeans.org/schema/orderResult">
<import namespace="http://enterprise.netbeans.org/bpel/BookOrderWSServiceWrapper"
location="BookOrderWSServiceWrapper.wsdl" importType="http://schemas.xmlsoap.org/wsdl/"/>

<import namespace="http://maciekm.com/" location="BookOrderWS/wsdl/BookOrderWSService.wsdl"
importType="http://schemas.xmlsoap.org/wsdl/"/>

<import namespace="http://enterprise.netbeans.org/bpel/PaymentWSServiceWrapper" location="PaymentWSServiceWrapper.wsdl"
importType="http://schemas.xmlsoap.org/wsdl/"/>

<import namespace="http://maciekm.com/" location="PaymentWS/wsdl/PaymentWSService.wsdl"
importType="http://schemas.xmlsoap.org/wsdl/"/>

<import namespace="http://j2ee.netbeans.org/wsdl/BookOrderBPEL/bookOrderProcessWSDL"
location="bookOrderProcessWSDL.wsdl" importType="http://schemas.xmlsoap.org/wsdl/"/>

<partnerLinks>
<partnerLink name="OrderProcessPL" xmlns:tns="http://j2ee.netbeans.org/wsdl/BookOrderBPEL/bookOrderProcessWSDL"
partnerLinkType="tns:bookOrderProcessWSDL" myRole="bookOrderProcessWSDLPortTypeRole"/>

<partnerLink name="BookOrderPL" xmlns:tns="http://enterprise.netbeans.org/bpel/BookOrderWSServiceWrapper"
partnerLinkType="tns:BookOrderWSLinkType" partnerRole="BookOrderWSRole"/>

<partnerLink name="PaymentPL" xmlns:tns="http://enterprise.netbeans.org/bpel/PaymentWSServiceWrapper"
partnerLinkType="tns:PaymentWSLinkType" partnerRole="PaymentWSRole"/>

</partnerLinks>
<variables>
<variable name="CancelOrderIn" xmlns:tns="http://maciekm.com/" messageType="tns:cancelOrder"/>
<variable name="OrderBookOut" xmlns:tns="http://maciekm.com/" messageType="tns:orderBookResponse"/>
<variable name="PayForOrderIn" xmlns:tns="http://maciekm.com/" messageType="tns:payForOrder"/>
<variable name="OrderBookIn" xmlns:tns="http://maciekm.com/" messageType="tns:orderBook"/>
<variable name="BookOrderProcessWSDLOperationOut"
xmlns:tns="http://j2ee.netbeans.org/wsdl/BookOrderBPEL/bookOrderProcessWSDL"
messageType="tns:bookOrderProcessWSDLOperationResponse"/>

<variable name="BookOrderProcessWSDLOperationIn"
xmlns:tns="http://j2ee.netbeans.org/wsdl/BookOrderBPEL/bookOrderProcessWSDL"
messageType="tns:bookOrderProcessWSDLOperationRequest"/>

</variables>
<sequence>
<receive name="ReceiveOrder" createInstance="yes" partnerLink="OrderProcessPL"
operation="bookOrderProcessWSDLOperation"
xmlns:tns="http://j2ee.netbeans.org/wsdl/BookOrderBPEL/bookOrderProcessWSDL"
portType="tns:bookOrderProcessWSDLPortType" variable="BookOrderProcessWSDLOperationIn"/>

<scope name="BookOrderScope">
<compensationHandler>
<sequence name="Sequence2s">
<assign name="Assign1">
<copy>
<from>$OrderBookOut.parameters/return</from>
<to>$CancelOrderIn.parameters/orderId</to>
</copy>
</assign>
<invoke name="CancelOrder" partnerLink="BookOrderPL" operation="cancelOrder"
portType="tns:BookOrderWS" inputVariable="CancelOrderIn" xmlns:tns="http://maciekm.com/"/>

</sequence>
</compensationHandler>
<sequence name="Sequence1">
<assign name="AssignToOrder">
<copy>
<from>$BookOrderProcessWSDLOperationIn.part1/ns0:bookId</from>
<to>$OrderBookIn.parameters/bookId</to>
</copy>
<copy>
<from>$BookOrderProcessWSDLOperationIn.part1/ns0:customerId</from>
<to>$OrderBookIn.parameters/customerId</to>
</copy>
</assign>
<invoke name="OrderBook" partnerLink="BookOrderPL" operation="orderBook"
xmlns:tns="http://maciekm.com/" portType="tns:BookOrderWS" inputVariable="OrderBookIn" outputVariable="OrderBookOut"/>

</sequence>
</scope>
<assign name="AssignToPay">
<copy>
<from>$BookOrderProcessWSDLOperationIn.part1/ns0:creditCardNumber</from>
<to>$PayForOrderIn.parameters/creditCardNumber</to>
</copy>
<copy>
<from>$OrderBookOut.parameters/return</from>
<to>$PayForOrderIn.parameters/orderId</to>
</copy>
</assign>
<invoke name="Pay" partnerLink="PaymentPL" operation="payForOrder" xmlns:tns="http://maciekm.com/"
portType="tns:PaymentWS" inputVariable="PayForOrderIn"/>

<assign name="AssignToReply">
<copy>
<from>true()</from>
<to>$BookOrderProcessWSDLOperationOut.part1/ns1:isCompleted</to>
</copy>
</assign>
<reply name="Reply" partnerLink="OrderProcessPL" operation="bookOrderProcessWSDLOperation"
xmlns:tns="http://j2ee.netbeans.org/wsdl/BookOrderBPEL/bookOrderProcessWSDL" portType="tns:bookOrderProcessWSDLPortType"
variable="BookOrderProcessWSDLOperationOut"/>

</sequence>
</process>
As you can see BookOrderScope defines its compensation handler. It calls cancelOrder method of BookOrderWS.

Composite application that connects all modules together:



Testing how it works
Composite application has a testing support in Netbeans IDE. We can use it to create test cases.
Test case one - sending valid order and payment information - processing works properly:
Order request for bookId: 1 from customerId: 2 - created orderId: 1274383922765
Retrieving schema at '', relative to 'file:/PaymentWSService.wsdl'.
Payment received for orderId: 1274383922765, creditNumber:1111
Payment completed!
Test case two - sending valid order, but credit card number that simulates error - order created by first WS is cancelled by compensation handler:
Order request for bookId: 1 from customerId: 2 - created orderId: 1274383962281
Payment received for orderId: 1274383962281, creditNumber:1234
Payment error!
The log message is null.
javax.ejb.EJBException
at com.sun.ejb.containers.BaseContainer.processSystemException(BaseContainer.java:3903)
at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:3803)
at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:3605)
........................................................................................................................
Canceling orderId: 1274383962281
It works indeed:)

PS. Whole application was just quick test, so forgive me messy code and totally naive business scenario;)

9 comments:

  1. Hi Maciej,
    I was wondering the difference between exception and compensation handlers. This article plus example made it very clear. Thank you very much :)

    ReplyDelete
  2. Hi,
    I currently don't have access to the PC where the source code is. I hope to have it available again in 2 weeks (I am moving right now;).

    Maciej

    ReplyDelete
  3. ok IF your PC ok could you send it to me at fatmas1982@gmail.com

    or could you send me your mail I want add you to messenger for interact with us more easy

    ReplyDelete
  4. Hi,
    I found the source code on my PC at my new house;) I sent it as a reply to your mail.

    Maciej

    ReplyDelete
  5. could you give me source cod

    ReplyDelete
  6. could you give me source code
    could you send it to me at ashyousef@gmail.com

    ReplyDelete
  7. Hi eng Youash I forward cod to you cod of eng Maciej Moczkowski at you mail

    ReplyDelete
  8. I would to thank you for your sending me the source code and i thank you again for eng, fatma gad and eng Maciej Moczkowski

    my best wishes and love for you

    ReplyDelete