Chapter 12. Building Validation into Services

Once we have divided our solution up into a number of composite components, one of the next questions we typically get is:

Where should I put my validation and how should I implement it?

At first glance this may seem like an obvious question, but once you consider that a service may be made up of other services, it becomes clear that you could potentially end up implementing the same validation in every level.

Apart from the obvious performance implications, you also have the issue of having to implement and maintain the same validation at multiple points within the solution.

When you get down to an individual service, you still have many considerations around where in the service you place the validation and how best to implement it. Particularly if you want the flexibility to be able to change the validation within a service without having to redeploy it.

This chapter gives guidance on how best to address this question. It examines how we can implement validation within a service using XSD validation, Schematron and Business Rules as well as within the service itself. With each of these options it looks at the pros and cons and how they can be combined to provide a flexible validation strategy.

Finally, we look at validation within the context of the overall solution, and provide guidelines around which layer within the architecture we should place our validation.

Using XML Schema validation

A service exposes one or more operations, these operations provide the entry point for the outside world, so it provides the obvious starting point for implementing validation in our service.

The interface for each of these services is defined by its WSDL contract, with the core structure of the data being exchanged being defined by the XML Schemas. So XSD validation provides an excellent way to implement the initial level of validation.

When implementing schema based validation, we have two basic approaches — which are either to implement strongly typed web services or loosely typed services.

Strongly typed services

With strongly typed services, we use XML Schema to very precisely specify the exact structure of each element within our XML instance. For example, if we look at the definition of CreditCard within the oBay canonical model a strongly typed version may be defined as follows:

<xsd:complexType name="tCreditCard">
<xsd:sequence>
<xsd:element name="cardType" type="tCardType"/>
<xsd:element name="cardHolderName" type="tCardHolderName"/>
<xsd:element name="cardNumber" type="tCardNumber" />
<xsd:element name="expiryMonth" type="tExpiryMonth"/>
<xsd:element name="expiryYear" type="tExpiryYear"/>
<xsd:element name="securityNo" type="tSecurityNo" />
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="tCardType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="MasterCard"/>
<xsd:enumeration value="Visa"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="tCardHolderName">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="32"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="tCardNumber">
<xsd:restriction base="xsd:integer">
<xsd:pattern value="[0-9]{16}"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="tExpiryMonth">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="1"/>
<xsd:maxInclusive value="12"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="tExpiryYear">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="2008"/>
<xsd:maxInclusive value="9999"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="tSecurityNo">
<xsd:restriction base="xsd:integer">
<xsd:pattern value="[0-9]{3}"/>
</xsd:restriction>
</xsd:simpleType>

With this approach, we have very precisely defined the following restrictions:

  • Valid card types are either MasterCard or Visa

  • Credit card number is a 16 digit integer

  • The expiry month must be between 1 and 12

  • The expiry year must be a four digit integer with a minimum value of 2008

  • The security code is a 3 digit integer

The advantage with this approach is that we have a far more explicit definition of the interface, providing a far more robust and tightly controlled entry point for our service. From a client perspective, it provides a far clearer understanding of what does or doesn't constitute a valid data structure.

From an implementation perspective, by placing the majority of the validation in the service contract we have removed the need for the underlying service to build in this validation, simplifying the construction of the actual service.

However, the major disadvantage with this approach is that the tighter the constraints, then the more resistant to change a service becomes.

For example, if oBay decided to accept American Express as payment, then CardType would need to be updated to contain an additional enumeration, CardNumber would need to be amended to accept 15 digit numbers and SecurityCode amended to accept 4 digit numbers.

This would require oBay to release a new version of their XML Schema and a corresponding new version of any service which relies on CreditCard in any of its operations.

In addition, every New Year, a new version of the canonical model would be required to update ExpiryYear as appropriate.

Note

You could also argue that it's perfectly valid to have details of an expired credit card, in which case you would not want to put this constraint in the Canonical Data Model.

Loosely typed services

With a loosely typed approach, we use XML Schema to define the overall structure of the XML instance, that is, what elements may appear in the document, whether they are optional or mandatory and how often they may occur, but provided minimal constraints around the content of each element. Using this approach our definition of CreditCard could be as follows:

<xsd:complexType name="creditCard">
<xsd:sequence>
<xsd:element name="cardType" type="xsd:string"/>
<xsd:element name="cardHolderName" type="xsd:string"/>
<xsd:element name="cardNumber" type="xsd:integer"/>
<xsd:element name="expiryMonth" type="xsd:integer"/>
<xsd:element name="expiryYear" type="xsd:integer"/>
<xsd:element name="securityNo" type="xsd:integer"/>
</xsd:sequence>
</xsd:complexType>

This is about as loose a definition as we could provide, though we could have gone one step further and made every element a string.

The major advantage of this approach is that the service is far more conducive to change. Following on from our previous example, if oBay decided to accept American Express as payment, then no changes would be required to the schema or service contract.

However, the key disadvantage with this approach is that we have far less control over the data that comes into our service and thus need to rely on the required validation being implemented elsewhere within our service.

What we want to avoid is coding the majority of this validation into the service itself, as this can over-complicate the implementation of the service, resulting in the same validation being implemented in multiple services (possibly inconsistently) as well as making changed harder to manage, as we would have to update the service code every time the validation rules changed.

Another disadvantage with this approach is that the service contract provides far less guidance to the consumer of the service as to what constitutes valid data, thus additional documentation would be required along with the service to define this.

Combined approach

Rather than use one approach exclusively, the key here is to strike the right balance and use schema validation to provide at least an initial sanity check of the data, that is, the data is of correct type (for example integer, date) and that the size of the data is within reasonable limits.

For example, with the loosely coupled schema definition, all our fields could be of any length. Often this data will at some point be persisted in a database, if a service consumer issued a request with elements containing data larger than the underlying data store then this could cause the service to fail.

These types of validations can easily be overlooked by developers, yet cause problems that are hard to diagnose at run time; by ensuring that we perform some level of sanity checking at the service entry point we can prevent these issues from occurring.

This also prevents services being called with significantly oversized payloads, which could have performance implications for the system if allowed to permeate through the application.

For elements which are far less prone to change, we can define tighter constraints around the content of those elements, to remove as much validation as possible from the underlying service implementation.

However, we would still like to abstract out as much of the validation logic from the underlying service as possible. This not only makes the service simpler to implement, it also makes the service more reusable as we could potentially provide different validation depending on the context in which it is used. Fortunately this is where Schematron comes in.

Using schema validation within BPEL PM

Schema validation of incoming and outgoing XML documents is enabled within BPEL PM, through appropriate configuration of the validateXML property; this can take one of the following three values:

  • strict: Schema validation will be applied to the XML document, if the XML document fails validation then an exception will be thrown.

  • warning: Schema validation will be applied to the XML document, if the XML document fails validation then BPEL will log this in the audit trail of the appropriate activity (that is, invoke, reply, receive), but no exception is thrown.

  • none: No schema validation is applied.

This property is set at the domain level within BPEL, with a default setting of none, that we can then override at the Partner Link level by configuring it appropriately.

Note

Note in releases prior to 10.1.3.4, this property takes the value of true and false, which maps to strict and none respectively.

Validation of inbound documents

Within a BPEL process, the receive activity will validate inbound documents if its corresponding Partner Link is configured (either at the Partner Link or domain level) to validate XML. If the validation results in an exception being thrown, then the receive activity will throw the exception.

For a synchronous operation, if the BPEL process doesn't handle the exception then the receive activity will return a fault to the client. However, for an asynchronous operation no fault is returned to the client.

Validation of outbound documents

Within a BPEL process, the invoke and reply activities will validate outbound documents if their corresponding Partner Link is configured to validate XML. As with inbound documents, if the validation results in an exception being thrown, then the corresponding invoke or reply activities will throw the exception.

Validation between BPEL processes

If we have a BPEL process invoking another BPEL process, there are some subtle nuances we need to be aware of. This is because there are two possible points where the validation could occur.

Validation for synchronous interactions

Consider the following synchronous interaction, shown as follows:

Validation for synchronous interactions

The first point where XML validation could occur is in the invoke activity in process A, the second in the receive activity in process B. If the validation is carried out in the invoke activity, and an exception is raised, then no message will be sent and process B will never be invoked.

However, if the validation is carried out in the receive activity in process B, the message will be sent and an instance of process B will be created to process the message.

Assuming the exception isn't handled in process B, then the receive activity will return a fault to process A, which will be re-thrown by the invoke activity in process A. This is the same fault that the invoke would have thrown if it carried out the validation.

So functionally it makes no difference, but if we carry out the validation in the receive we have the overhead of sending the message and instantiating a new instance of process B.

Validation for asynchronous interactions

Consider a similar scenario but this time for an asynchronous interaction shown as follows:

Validation for asynchronous interactions

As before, the validation could occur in either the invoke or receive activity, if the invoke activity carries out the validation, the result will be identical to our synchronous example.

If the validation happens in the receive activity, we get a different result. This is because, with asynchronous interactions, the message isn't delivered directly to the process, rather it's placed in the message delivery queue.

The message will then be removed from the queue and delivered to process B; if an error occurs then the receive activity will throw an exception, which if it isn't handled will result in the message being placed back in the queue for recovery at a later point in time.

If this happens then process A will potentially wait forever for a response to come. So in this scenario, we should look to either carry out the validation in the initial invoke, or use a fault handler to catch any validation exceptions raised by the receive activity.

Setting validateXML for a BPEL domain

The BPEL Console is used to configure the validateXML property for a BPEL domain. To do this log in to the console for the required domain and click on the Configuration tab, from here click on the Domain sub tab.

This will list all the configuration properties for the BPEL domain, if you scroll to the bottom of the list you will see the validateXML property, shown in the following screenshot:

Setting validateXML for a BPEL domain

Modify this to the required value and click Apply; this will update the property to contain the new value and apply it with immediate effect.

Setting validateXML for a PartnerLink

To specify the validateXML property for a PartnerLink, open the Partner Link within the BPEL process and select the Property tab shown as follows:

Setting validateXML for a PartnerLink

Next click Create. This will pop up a window with a drop down containing properties you can set on the Partner Link. Select validateXML and then for the Property Value specify the required setting.

When finished, save the BPEL process and deploy it to the BPEL Server in order for it to take effect.

Using schema validation within the service bus

Within the Service Bus, schema validation is carried out using a Validate action, which is typically invoked during a pipeline stage or route node.

The Validate action provides an additional degree of flexibility when compared to BPEL PM, in that rather than validate the entire payload of either an inbound or outbound document, you can choose to validate just a fragment of the XML document which you specify using XPath.

Upon completing validation, you can specify that the Validate action records the result of the validation, either true or false in a variable or that it should throw an exception if the validation fails.

Validation of inbound documents

When processing an inbound document, it's good practice to perform validation as early as practical within the flow as this prevents unnecessary processing of an invalid document.

This typically means creating a validation stage at the first stage within the Request Pipeline of a pipeline pair.

If we look at the operation updateCreditCard which forms part of the UserAccount service, a typical XML instance for this operation would appear as follows:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope">
<soap:Body xmlns:tns="http://xmlns.packtpub.com/obay/bs/UserAccount"
xmlns:usr="http://schemas.packtpub.com/obay/usr"
xmlns:cmn="http://schemas.packtpub.com/obay/cmn">
<tns:updateCreditCard>
<usr:userId>jsmith</usr:userId>
<cmn:creditCard>
<cmn:cardType>MasterCard</cmn:cardType>
<cmn:cardHolderName>John Smith</cmn:cardHolderName>
<cmn:cardNumber>4570126723982904</cmn:cardNumber>
<cmn:expiryMonth>10</cmn:expiryMonth>
<cmn:expiryYear>2010</cmn:expiryYear>
<cmn:securityNo>528</cmn:securityNo>
</cmn:creditCard>
</tns:updateCreditCard>
</soap:Body>
</soap:Envelope>

If we wanted to add a validation step to check the creditCard details, then we would add a validation stage (for example, Validate Credit Card) at the start of the Request Pipeline.

To add a validation action, click on the validate stage within the Request Pipeline and select Edit Stage. This will bring up the Edit Stage Configuration window. Click on Add an Action | Message Processing | Validate. This will insert the following Validate action into our stage.

Validation of inbound documents

In the variable text field, enter the name of the variable which contains the XML fragment we wish to validate, that is, body in our example. Next, we need to specify which part of the body variable we want to validate. To do this, click the <XPath> link to bring up the XPath Expression Editor and define the appropriate XPath expression.

In our case we want to validate the creditCard fragment from our body variable, so our expression is defined as follows:

./tns:updateCreditCardProcessRequest/cmn:creditCard

Next we need to specify which schema element or type we wish to validate against; click on the <Resource> link and select Schema from the drop down, this will display the Select a XML Schema window, as shown in the following screenshot:

Validation of inbound documents

Select the required schema, that is, common_v1 in our case and this will launch the Select a Schema definition window, shown as follows:

Validation of inbound documents

This lists all the types and global elements defined in the XML Schema. From here we select the element or type we wish to validate our XML fragment against. So for our example, select tCreditCard and hit Submit.

Our completed validate action will look as follows:

Validation of inbound documents

At run time, if the validation fails then the validate action will throw an exception. Typically we would define a Stage Error Handler for our validation stage to catch the exception and handle it appropriately; we look at how to do this in Chapter 13Error Handling. If we don't define an error handler, the Service Bus will return the default validation fault to the caller of the service.

Validation of outbound documents

Within the Service Bus we can also use the validate action to check any outbound documents. Typically we would do this just prior to invoking any external service, and we would do this in a similar fashion for inbound documents.

However, strictly speaking, if we have received a valid inbound document and our service has been correctly implemented, it shouldn't be generating any invalid XML.

In reality this is not always the case, so in many scenarios it still makes sense to include this level of validation. Though if we follow this approach too strictly we run the risk of over-validation which we will cover in more detail in the following section.

Using Schematron for validation

Schematron provides another means of validating the message payload of a web service. It takes a markedly different approach from schema validation, in that rather than check the overall structure of the XML instance, it enables you to specify one or more assertions that we wish to enforce. If all these assertions are met the document is deemed to be valid.

These assertions are specified using XPath, so it allows us to specify constraints that can't be expressed using XML Schema. For example following on from the example above, we can define the following validations on credit card.

  • If the card type is American Express then the card number should be 15 digits in length, otherwise it should be 16 digits.

  • If the card type is American Express then the security code should be 4 digits in length, otherwise it should be 3 digits.

  • The expiry date, which consists of the expiryMonth and expiryYear elements, should be in the future.

For each assertion we can also specify meaningful diagnostic messages, which indicate why an assertion hasn't been met (as opposed to schema validation messages which aren't always so enlightening).

The other advantage of using Schematron is that it enables us to modify the assertions for a document without the need to change the schema.

However, rather than consider Schematron as alternative approach to XML Schema validation, we see it very much as complementary. Thus we would use XML Schema to validate the core structure of the XML, but not make those checks too granular. Rather we will place those checks along with ones that can't be expressed in XML Schema in Schematron.

Overview of Schematron

One of the advantages of Schematron is that being based on XSLT makes it extremely easy to learn. Effectively it has several key constructs, and once these are understood you are ready to unleash the full power of the tool.

So before we look at how to use Schematron within SOA Suite, we will give a quick introduction to Schematron itself. Readers who are familiar with Schematron may still want to skim this section, just to understand some of the idiosyncrasies of how Schematron behaves within the Oracle SOA Suite.

If we look at the operation updateCreditCard which forms part of the UserAccount service, a typical XML instance for this operation would appear as follows:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope">
<soap:Body xmlns:tns="http://xmlns.packtpub.com/obay/bs/UserAccount"
xmlns:usr="http://schemas.packtpub.com/obay/usr"
xmlns:cmn="http://schemas.packtpub.com/obay/cmn">
<tns:updateCreditCard>
<usr:userId>jsmith</usr:userId>
<cmn:creditCard>
<cmn:cardType>MasterCard</cmn:cardType>
<cmn:cardHolderName>John Smith</cmn:cardHolderName>
<cmn:cardNumber>4570126723982904</cmn:cardNumber>
<cmn:expiryMonth>10</cmn:expiryMonth>
<cmn:expiryYear>2010</cmn:expiryYear>
<cmn:securityNo>5285</cmn:securityNo>
</cmn:creditCard>
</tns:updateCreditCard>
</soap:Body>
</soap:Envelope>

A Schematron which checks that the credit card type is MasterCard or Visa could be written as follows:

<?xml version="1.0" encoding="UTF-8"?>
 
<schema xmlns="http://www.ascc.net/xml/schematron">

<ns uri="http://schemas.packtpub.com/obay/cmn" prefix="cmn"/>
 <pattern name="Check Credit Card Type">
 <rule context="cmn:creditCard">
 <assert test="cmn:cardType='MasterCard' or
cmn:cardType='Visa'">
Credit Card must be MasterCard or Visa
 </assert>

 </rule>

 </pattern>

 </schema>

From this we can see a Schematron is made of four key components: pattern, rule, asser, and ns contained within the schema element. We'll examine these elements one by one, starting with the inner most element and working out.

Assertions

The assert element as its name suggest is used to define the constraints to be enforced within an XML document. In the above Schematron we have defined the following assert element.

<assert  test="cmn:cardType = 'MasterCard' or cmn:cardType = 'Visa'">

Credit Card must be MasterCard or Visa
</assert>

We can see it contains the test attribute which specifies an XPath expression, which should return a boolean value. If the test expression evaluates to true, then the assertion has been met.

If the test evaluates to false then the assertion has failed and the document is invalid. When this happens Schematron will raise an error and the content of the assert element (i.e. Credit Card must be MasterCard or Visa) is returned as an error message.

Rules

Asserts are defined within a rule element. Each rule has a context attribute, which contains an XPath expression used to specify the nodes within an XML instance to which the rule should be applied.

In effect it will perform a select on the root node of the service payload, which may result in a node set containing zero, one or more nodes. Each node returned will then be validated against all asserts defined within the rule.

In the case of the valCreditCard.sch Schematron, we have defined the following rule:

<rule context="cmn:creditCard">
...
</rule>

We have specified a relative context of cmn:creditCard, which will match against any occurrence of creditCard regardless of where it appears in the XML payload. The advantage of this is that we can use the same Schematron to validate any occurrence of cmn:creditCard regardless of which operation it is used in.

In the case of the updateCreditCard operation, the rule will return just a single node, cmn:CreditCard shown as follows, to which our single assertion will be applied.

<cmn:creditCard>
<cmn:cardType>MasterCard</cmn:cardType>
<cmn:cardHolderName>John Smith</cmn:cardHolderName>
<cmn:expiryMonth>10</cmn:expiryMonth>
<cmn:expiryYear>2010</cmn:expiryYear>
<cmn:securityNo>5285</cmn:securityNo>
</cmn:creditCard>

In the case where we have multiple assertions defined for a rule, if more than one assertion fails for a particular node, then Schematron will return a diagnostic message for each failed assertion.

Patterns

Rules are defined with a pattern element. Each pattern can hold a collection of one or more associated rules. Pattern contains a single attribute name, which contains free format text used to describe the rules contained within it.

In our valCreditCard.sch Schematron, we have defined the following pattern:

<pattern name="Check Credit Card Type">
...
</pattern>

When processing an XML instance, Schematron will apply each pattern against the XML instance in pattern order. When checking against a pattern, Schematron will check the XML instance against each rule contained within the pattern in rule order.

Namespaces

Namespaces are declared using the ns element. This has two attributes uri which is used to define the namespace URI and prefix which is used to define the namespace prefix.

For example, in our credit card validation Schematron we define the namespace http://schemas.packtpub.com/obay/cmn with the following:

<ns uri="http://schemas.packtpub.com/obay/cmn" prefix="cmn"/>

Schema

The root element of a Schematron document is the schema element defined within the namespace http://www.ascc.net/xml/schematron. In our example we've made this the default namespace so we don't have to prefix any of the Schematron elements.

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.ascc.net/xml/schematron">
...
</schema

Intermediate validation

So far we have just implemented some basic validation that we could have quite easily have performed using XML Schema. However, to give you a feel for the real capability of Schematron we will also look at some validation requirements that can't be implemented using XSD.

Cross field validation

An area where Schematron excels is cross field validation, for example if we wanted to check that cardNumber is 16 digits long for MasterCard and Visa, and 15 digits long for American Express, we could write the following assertion:

<rule context="cmn:CreditCard">
<assert test="((cmn:cardType='MasterCard' or
cmn:cardType='Visa') and
string-length(cmn:cardNumber) = '16') or
(cmn:cardType='American Express' and
string-length(cmn:cardNumber) = '15')">
Invalid Card Number.
</assert>
</rule>

Using XPath predicates in rules

The above approach, though perfectly valid, could become quite verbose, especially once we start to add additional checks for specific card types. For example, in case we want to check the length of securityCode based on cardType.

Another approach is to use an XPath predicate within the rules context attribute to narrow down the context to a specific card type. For example we can specify a set of assertions for credit cards of type MasterCard as follows:

<rule context="cmn:creditCard [cmn:cardType='MasterCard']">

<assert test="string-length(cmn:cardNumber) = '16'">
Mastercard card number must be 16 digits.
</assert>
<assert test="string-length(cmn:securityNo) = '3'">
Security code for Mastercard must be 3 digits.
</assert>
</rule>

Using this approach we can specify a different rule for each card type, allowing us to maintain assertions for each card type independently from one another as well as simplifying the process of adding new card types.

Using XPath 2.0 functions

In the above assertion we are just testing that cardNumber is 16 characters in length, we are not checking that it's an actual integer, rather we are relying on schema validation for this.

There is nothing wrong with this approach, but what if some cards allowed alphanumeric numbers? In this scenario we would need to declare cardNumber as a string and then carry out specific validation in Schematron to check the format of the element based on cardType.

For this we can use the matches function, to test whether the content of the element conforms to a particular regular expression. However, this is an XPath 2.0 function, so in order to use this within Schematron we need to define its namespace. We do this in exactly the same way as we would for any other namespace, that is:

<ns uri="http://www.oracle.com/XSL/Transform/java/oracle.tip. pc.services.functions.Xpath20" prefix=" xp20"/>

We can then create an assertion that matches the cardNumber as shown:

<assert test=" xp20:matches(cmn:cardNumber, '[0-9]{16}')">
Mastercard number must be 16 digits.
</assert>

Date validation

Schematron is also an excellent method for validating dates based on the current time, for example we need to check that the expiry date for the credit card is not in the past.

To do this we need to check that the expiry year of the credit card is greater than the current year, or that the expiry year of the credit card equals the current year and the current month is less than or equal to the expiry month of the card.

To do this we could write the following test:

cmn:expiryYear > xp20:year-from-dateTime(xp20:current-dateTime()) or (cmn:expiryYear= xp20:year-from-dateTime(xp20:current-dateTime()) and
cmn:expiryMonth>=xp20:month-from-dateTime(xp20:current-dateTime()) )

Element present

Another requirement is to check whether an element is present or not. We can do this with XML Schema by defining an element as being mandatory. However, whether an element is optional or mandatory may well be based on values in other fields.

For example, if we had made securityNo optional within our schema definition, but we wanted to make it mandatory for American Express, we could write the following rule:

<rule context="cmn:creditCard[cmn:cardType='American Express']">
<assert test="cmn:securityNo">
Security No must be specified
</assert>
</rule>

Note, this will only check to see if the element is present in the XML instance. It doesn't actually check if it actually contains a value. The simplest way to check this is to use the string-length function as shown:

<rule context="cmn:creditCard[cmn:cardType='American Express']">
<assert test="cmn:securityNo and string-length(cmn:securityNo)>0">
Security No must be specified
</assert>
</rule>

Using Schematron within BPEL PM

BPEL PM includes a Schematron validation service which allows you to validate an XML instance against a Schematron. The service is invoked just like any other service via a partner link, and in the event of a validation failure, returns a ValidationException fault that contains a list of errors. We can then use a fault handler to catch the fault and handle it appropriately.

Creating a Partner Link for the Validation Service

The WSDL file for the validation service is contained in the bpm-services.jar that is installed as part of the SOA Suite. This file can be located in the directory:

<SOA_HOME>pelsystemserviceslib

Take a copy of this JAR, and extract the file:

oracle	ippcservicesvalidationvalidation.wsdl

We can then use this WSDL to define a Partner Link in the normal way. The validationService defines a single operation validation. It takes two parts; instanceFile which contains the XML to validate and ruleFile which contains the Schematron.

Note

If you look at the bindings section of the WSDL you will see that it provides this service via java bindings, meaning that the validation service is invoked via java (as opposed to SOAP over HTTP), giving us better performance. This is an area that we will examine in more detail in Chapter 17The importance of Bindings.

Creating a Schematron file

To create a Schematron within our BPEL project, in the Applications Navigator right-click on your BPEL Project and select New. This will launch the gallery for creating new project resources, from here select File (under the General category) and click OK.

This will bring up the Create File dialogue; give the file an appropriate name (for example valCreditCard.sch) and click OK. This will create the file in the resources folder of our BPEL project.

Creating the file in the resource folder ensures that JDeveloper will include the file in BPEL Suitcase when we deploy the BPEL process to the server, which will allow the BPEL process to easily access the content of the file at runtime.

Once defined, we can use JDeveloper to edit the file in order to define our Schematron.

Invoking the validate operation

To invoke the validate service, drag an invoke activity onto our BPEL process in the normal way and create an input and output variable.

The input variable takes two parts: instanceFile which contains the XML to validate and ruleFile which contains the Schematron. The following figure shows the structure of the input it expects.

Invoking the validate operation

Assigning the instanceFile

The instanceFile contains a single element; validation defined as an xsd:anyType; which we need to populate with the XML to be validated.

Note, the validation operation expects the validation element to contain a single element; any more than this will cause it to return an error.

So in our example, if we use a standard copy operation such as the following:

<copy>
<from variable="inputVariable" part="payload"
query="/client:updateCreditCard"/>
<to variable="schematronInput" part="instanceFile"
query=/ns4:validation/>
</copy>

To map the content of updateCreditCard to validation, we will end up with a validation element that looks like the following:

<validation xmlns="http://xmlns.oracle.com/pcbpel/validationservice">
<usr:userId>jsmith</usr:userId>
<cmn:creditCard>
<cmn:cardType>MasterCard</cmn:cardType>
<cmn:cardHolderName>John Smith</cmn:cardHolderName>
<cmn:cardNumber>4570126723982904</cmn:cardNumber>
<cmn:expiryMonth>10</cmn:expiryMonth>
<cmn:expiryYear>2010</cmn:expiryYear>
<cmn:securityNo>285</cmn:securityNo>
</cmn:creditCard>
</validation>

Here, we can see the validation element contains the two elements: userId and creditCard, which if we submitted to the validation service would cause it to throw an error as it expects only a single element.

One way to fix this is to add a remove operation to our assign activity, to delete the userId element. To do this, within the Assign activity click on Create and select Remove Operation as shown in the screenshot:

Assigning the instanceFile

This will present us with the Create Remove Operation window, as shown in the following screenshot:

Assigning the instanceFile

Here we just need to specify the node that we wish to delete, so in our example we just expand the schematronInput tree and select ns4:validation.

Since this is defined as xsd:anyType we will need to manually enter the XPath expression to remove the required node, which in our example is /ns4:validation/usr:userId.

Assigning the ruleFile

The ruleFile contains a single element; schema defined as an xsd:anyType, which needs to contain the Schematron we wish to validate our XML against.

To copy the content of our Schematron file (for example, valCreditCard.sch) into schema we can use the ora:doc XPath function, found under the category BPEL XPath Extension Functions in the Expression Builder.

This function reads in the content of the specified file, parses it and returns it as an XML node, so for our purposes we have the following copy command:

<copy>
<from expression="ora:doc('valCreditCard.sch')"/>
<to variable="schematronInput" part="ruleFile"/>
</copy>

Sharing a Schematron between processes

As we have already observed, our valCreditCard Schematron can be used to validate a creditCard element in any BPEL Process in which it is used.

Rather than create a copy of the same Schematron file in each BPEL project which uses it, a better approach is to have a single copy of the Schematron that is referenced by all processes.

As with XML Schemas, BPEL PM also allows you to deploy any other XML document directly to the server, by placing it under the same directory structure, namely:

<SOA_HOME>pelsystemxmllib

The XML document will then be made available at the following URL:

http://server:port/orabpel/xmllib/<document name>

We can still use the ora:doc XPath function as before to read in the content of the file, but rather than specify a relative file name we now need to specify its URL.

For example, if we placed valCreditCard.sch in the following directory:

<SOA_HOME>pelsystemxmllibobaysch

We would modify the above call to ora:doc to the following:

ora:doc('http://host:port/orabpel/xmllib/obay/sch/valCreditCard.sch')

This has a number of distinct advantages. Firstly, you can ensure all your processes use the same version of a particular Schematron. Secondly, if we need to modify our validation rules we just need to update a single Schematron and redeploy it to the server. In addition any BPEL process which references that Schematron will automatically pick up the modified version, without us having to redeploy it.

Using Schematron with the service bus

The service bus does not support Schematron validation. However, it does provide a Java Callout Action that allows you to invoke a Java method within a message flow.

One approach would be to implement a lightweight Java class which wrappers the Schematron classes and exposes a single method (similar to the Schematron validation service in BPEL) which can then be invoked using a Java Action in the service bus.

Putting validation in the underlying service

So far we have looked at using XML Schema and Schematron to put validation either in the service contract or mediator layer in order to provide initial validation of a service invocation, before we actually invoke the underlying service. This provides a number of benefits, including:

  • Simplifies the development of validation within the actual service as it can now rely on the fact that it is receiving relatively sensible data.

  • Allows us to implement a more generic service, since business specific validation can be provided at a higher level within the service. This makes the service more reusable, as it can be used in multiple scenarios each with different validation requirements.

  • Makes change easier to manage, as changes to business rules which impact the overall validation of the service can happen at either the schema or Schematron level and thus may require no changes to the actual underlying service

  • By placing the validation in a centralised place, which can be reused across multiple services, it enables us to implement the same validation across multiple services in a consistent fashion. This also makes change simpler to manage as we only have to make the changes once as opposed to everywhere the validation is required.

However, at some point, we will still be required to put some level of validation in the underlying service itself. For example, take our updateCreditCard operation, despite all our checks we can't be completely sure that the credit card itself is actually a valid card and that the card name, security number, and so on correspond to the given card number. To validate this, we will still need to call out to an external validation service.

In addition, we still need to validate that the userId provided as part of the operation is a valid user within our system.

Using Business Rules for validation

One option for implementing validation checks within your service is to separate them out as a Business Rule. This allows us to implement the validation just once and then share it across multiple services. This shares a number of advantages with the approaches already discussed, including:

  • Simplifies development of rules, as we only need to implement it once

  • Rules are implemented consistently across multiple services

  • Easier to maintain as rules only need to be modified once should a change be required

When implementing a service using BPEL, the use of rules for validation is a pretty natural fit. But natively rules is implemented in Java, so comes with a Java API making it relatively straightforward to call from any services implemented in Java.

In addition, you can also expose a rule set as a web service, either in the standard way you would expose a Java code as a web service, or just by wrapping the rule in a decision service embedded within a Synchronous BPEL process.

Coding in validation

While providing an extra option for validation, using Business Rules will not be appropriate in every case. In these scenarios the only remaining option is to implement the validation in code.

However, even when we take this approach, we can still follow the same pattern that we used for Business Rules, namely to separate out the validation from the core service so that it can be used in multiple services and also consider the option of providing a means to maintain the validation rules without the need to modify actual code.

Returning validation failures in synchronous services

When putting the validation in the underlying service, apart from carrying out the validation we also need a mechanism for returning any validation failures to the client, ideally with some meaningful information about why the error occurred.

For synchronous services the mechanism for achieving this is to return a SOAP Fault to the service caller. A SOAP Fault contains four pieces of information, namely:

  • faultcode: This provides a high-level indication as to the cause of the fault. SOAP 1.1 defines the following fault codes: VersionMismatch, MustUnderstand, Client, or Server. As the fault is as a result of an error in the message payload which the client needs to fix, we should return a fault code of type Client, unless we are returning a custom fault code.

  • faultstring: This should contain a human-readable description of why the fault occurred, that is, the reason for the validation failure.

  • faultactor: This provides details of where in the message path the fault occurred. If the failure occurred somewhere other than the final destination of the SOAP message then this must be present to indicate where. For our purpose, we can leave this blank.

  • detail: This is an optional element, which can be used to provide further detail about why the fault occurred. We only need to provide this if the faultstring does not provide sufficient information for the client to handle the error.

Defining faults

Unless returning a basic fault, that is, using a predefined fault code and no structured content within the soap:detail, it is good practice to define the fault as part of the WSDL contract defining your service.

Faults are defined by adding the appropriate fault elements to the operation declarations. A fault has two attributes: name which corresponds to the fault code returned in the soap fault and message which will contain additional information about the fault and is returned within the soap:detail element.

For example to define a fault for the updateCreditCard operation we would just add the following fault element to our definition, shown as follows:

<operation name="updateCreditCard">
<input message="tns:updateCreditCard "/>
<output message="tns:updateCreditCardResponse "/>
 <fault name="tns:invalidCreditCard"
message="tns:invalidCreditCardFault "/>

</operation>

There is nothing to stop a service returning a fault which is undeclared in its service contract. However, by declaring the fault, the service consumer has the opportunity to handle the fault in an appropriate manner. By knowing the structure of the fault detail is able to process it in a more meaningful way.

Custom fault codes

Often it is desirable to define a custom fault, particularly for services that may return a number of faults, as this can simplify fault handling for the service consumer (because they can implement targeted fault-handling mechanisms for each type of fault).

SOAP 1.1 allows custom fault codes to be implemented through the use of the dot notation, for example we could define a fault code of client.invalidCreditCard in the SOAP namespace (http://schemas.xmlsoap.org/soap/envelope/ ). However, this can result in namespace collision and interoperability issues so is not WS-I Basic Profile compliant, and should be avoided.

Instead custom fault codes should be defined within their own namespace, for example we have defined our invalidCreditCard fault code to be in the same namespace as the actual userManagement service.

Note

While defining custom faults within their own namespace is WS-I Basic Profile compliant, WS-I Basic Profile still encourages that you use the standard SOAP 1.1 fault codes and use the detail element to provide any extra information.

Validation failures in asynchronous services

If an asynchronous service needs to return a fault to a client, it can't do this in the reply message in the same way that a synchronous service can. This is because an asynchronous service consists of two one-way operations, the first containing the original request, the second a callback from the service containing the result.

To return a fault we need to do this within the callback. We have two basic options to choose from, the first is to return the success or otherwise with the content of the standard callback and allow the client to inspect the content to determine whether the service was successfully invoked or not.

The other is to define additional operations on the callback port specifically for the purpose of returning an error message. The latter of these is the preferred approach as it allows the client to implement separate handlers for callbacks indicating errors (in much the same way we can implement separate fault handlers for each type of fault returned with synchronous services).

In many ways it's helpful to think of the operation name as being the equivalent of the fault code, and the message payload of the operation can be used to hold the equivalent of the remainder of the fault information (for example, fault string and detail).

For example, one way to define an asynchronous version of our updateCreditCard operation is as follows:

<portType name="userManagement">
<operation name="updateCreditCard">
<input message="usr:updateCreditCard "/>
</operation>
</portType>
<portType name="userManagementCallback">
<operation name="updateCreditCardCallback">
<input message=" usr:updateCreditCardCallback "/>
</operation>
 <operation name="invalidCreditCard">
<input message="usr:invalidCreditCard"/>
</operation>

</portType>

The final callback operation (highlighted in the code), is the equivalent of the fault defined within our synchronous service.

Layered validation considerations

Within a single composite application we have a certain amount of control over what validation to put in the schema, Schematron, and the underlying services. This allows us to design and implement these in a coordinated fashion so that they can work in synergy with one another.

However, once we start assembling services from other composite applications, then the lines of demarcation and thus which service is responsible for which validation becomes less clear.

There are a number of potential strategies which can be adopted, but each has its own strength and weaknesses. We examine some of these in the following sections, but in reality there is not always a simple answer and it really comes down to good design and being aware of the issues.

Dangers of over validation

Probably the "safest" approach is to make each service fully responsible for its own validation, and thus perform whatever validation is required regardless of what validation is performed by any other service in the chain.

However, this could have potential performance implications. Apart from the obvious overhead of performing the same work several times, it could introduce potential points of contention within the system.

If we take the updateCreditCard operation, at some point our application will need to fully validate the card. To do this, it will need to call out to an external web service. If we follow the approach of performing this validation in every service involved in the operation, and the request has to go through n layers of services, then that would require n callouts to the external service with the implied latency of making n callouts. Not to mention that the card company might wonder why this card is being validated so many times!

Another issue with this approach is that the validation may be implemented several times, not always identically, resulting in inconsistent validation that is hard to change. This can be addressed by using shared XML Schema, Schematron, and Business Rules validation.

Dangers of under validation

An alternate approach is to push the responsibility of validation down to the lowest service in the chain on the basis that if an error occurs, then it will catch the error which will be propagated up the chain and returned to the original consumer of the service.

Again on the surface this approach seems fine. However, the main issue here is if we have to undo any work as a result of the error, which we could have avoided if we had caught it earlier. For example, if have a service A which is a composite composed of service B and service C, the call to service B may succeed and only the call to C fail, in which case we may need to undo any work carried out by service B.

Negative coupling of validation

Another issue that arises with service composition is that a high level component which calls other components effectively inherits the validation of the lower level components.

The strategy we recommend here is that we put the minimal amount of validation in the lower-level component and put the more restrictive constraints in the higher level components.

Assuming the service is only designed for internal use, that is via other components within our control, this approach works well. We can mandate that any additional validation that is required is applied in a higher level component.

For those components that we need to expose directly to external consumers, we can still follow this approach, by implementing a wrapper component with the required validation and then exposing this externally.

This approach allows us to develop more generic lower-level components, which are easier to reuse while at the same time minimizing over and under validation.

Summary

In this chapter, we've looked at how we can implement validation within an individual service through a combination of XSD validation, Schematron, and Business Rules.

Ideally we should use XSD validation to check the overall sanity of the data, but in order to provide a greater level of flexibility abstract out the business specific validation into a separate component such as Schematron. This provides greater flexibility to change the validation for a component without the need to redeploy a new version of it.

In situations where Schematron can't provide the required validation, we've looked at how we can use Business Rules to build this into the underlying service implementation, again giving us the flexibility to change the validation without having to redeploy the service.

Finally, we've looked at some of the issues and potential strategies for validation when combing multiple services; while there are no simple solutions, by at least having an appreciation of the issues we are able to take these into account in the design of our overall solution.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.137.117