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
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
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
BookOrderWS is responsible for creating and canceling orders. It is implemented as EJB module and consists of one class:
package com.maciekm;PaymentWS is also EJB module with very simple code:
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);
}
}
package com.maciekm;Here our mock implementation simulates payment processing. Unpredicted exception will occur for 1234 credit card number.
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!");
}
}
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"?>Process diagram:
<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>
Code:
<?xml version="1.0" encoding="UTF-8"?>As you can see BookOrderScope defines its compensation handler. It calls cancelOrder method of BookOrderWS.
<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>
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: 1274383922765Test case two - sending valid order, but credit card number that simulates error - order created by first WS is cancelled by compensation handler:
Retrieving schema at '', relative to 'file:/PaymentWSService.wsdl'.
Payment received for orderId: 1274383922765, creditNumber:1111
Payment completed!
Order request for bookId: 1 from customerId: 2 - created orderId: 1274383962281It works indeed:)
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
PS. Whole application was just quick test, so forgive me messy code and totally naive business scenario;)