Search |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Implementing Validation Rules using Aspects
Tue, 2005-11-08
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Validation Framework | Tier | Usage | Limitation |
|---|---|---|---|
| JavaScript | Web client (browser) | Detect invalid data on the client before passing it to server. | Limited to data validation in the browser. JavaScript code is not reusable in other layers of the application. Also, the web user can disable JavaScript execution in the browser, meaning the validation code will not run. |
| Spring Validator | MVC (servlet container) | Used in the controller class to check the validity of data submitted by the client before sending it to the service or DAO layers. This way, invalid data is detected in the MVC layer rather than passing it all the way to application or database servers. | Limited to MVC layer. We can't use validation rules defined in the XML file in other layers of the application. It's also limited to basic validation; we can't really use it to define business-rules-based validations. |
| Commons Validator | MVC/service | This framework provides a configurable validation engine and reusable methods on primitive data validations. | We can't really use this framework as a robust business rules validation tool. |
| Custom Validation Framework | MVC/service/DAO | A customized solution, so we can design it to address specific validation requirements in enterprise applications. | Coding, testing, and maintenance overhead. May not be as extensive and flexible as the available validation frameworks. |
Nowadays, rules engines are gaining more popularity in encapsulating business and validation rules in enterprise applications to reduce the cost of managing the rules. Let's briefly take a look at the advantages of using a rules engine to capture business rules.
Rules engines collect complex decision-making logic and work with a group of rule sets, usually defined in XML files, to make decisions on what action needs occur when a specific condition is true. The main driving factor behind using a rules engine for managing rules is that business rules change very often and customers (end users) are always demanding that these changes be rolled out into a production environment as soon as possible to keep up with competitors. Managing business logic using a rules engine gives us the flexibility we need to keep up with the dynamic nature of business process.
Following are some of the rules engine frameworks (both open source and commercial) that are currently available:
In most Java enterprise applications, we have to implement client and server validations. For these validations, we could use different implementations on the client and server. For example, we could use Struts Validator (or Spring Validator) in the MVC layer and a custom validation framework on the server. But then we would have to change code in two places whenever validation rules change for a specific data field, as business requirements change from time to time.
Data validation cuts across different application layers and can be treated as a cross-cutting concern, just like other cross-cutting concerns like persistence, security, exception handling, and object caching. It makes a lot of sense to use aspects to add validation logic into existing application code.
Before we get into the details of a validation framework and its implementation using rules engine and aspects, let's compare the traditional way of implementing data validation in a Java EE application to an aspect-oriented data validation. The following table lists the steps involved in implementing data validation in a typical application.
| # | Task | Application Layer | Traditional | Aspect-Oriented |
|---|---|---|---|---|
| 1 | Client submits the form filled in with data. Check the data to make sure data format and values are acceptable. | Web client | Use JavaScript to process data validation in the browser. | Define validation rules using XML and a rules engine. |
| 2 | Validate data submitted by client for data accuracy. | MVC layer | Use Spring Validator or similar framework to perform data validation. | Write validation code outside of the actual application code. |
| 3 | Check the data again to make sure it's valid and passes business validation rules. | Service layer | Design and implement validation logic in the server code. | Define aspects to inject validation logic into the application code (MVC or service layers). |
| 4 | Flexibility | All layers | Validation code is tightly coupled with application code. Changes in validation code impacts the application code, and vice versa. | Loosely coupled and flexible, so validation and application code can change independently. |
I created a sample application called LoanProc to use the proposed AOP Data Validation framework. This application is a home loan processing application in a mortgage lending company. The LoanProc application source is in the sample code (see Resources below). Let's take a look at some of the objectives we want to accomplish in the AOP Validation framework.
To keep it simple for demonstration purposes, our sample loan application page includes four data fields to show how validation rules can be applied to fields with various data types and formats. These validations include basic checks on date of birth and SSN fields, and server validations on loan amount and loan to value (LTV) fields. Following table shows the details of these validations.
| Field Name | Data Type | Client Validation Rules | Server Validation Rules |
|---|---|---|---|
| Date of Birth | Date | Valid date | None |
| SSN | Number | Data must be a number, nine digits in length | None |
| Loan Amount | BigDecimal | Must be a number | Value cannot be more than 1,000,000 |
| LTV | Number | Must be a number. Value must be less than 1.0 | LTV cannot be more than 0.85. (This means that we don't want to offer a loan to those who are not putting down at least 15 percent of the loan amount as down payment.) |
It's a good practice to define the validation rules for each module or web page in a separate rules XML file. I defined loan application validation rules in a text file called LoanApplicationRules.xml.
I used different open source technologies and frameworks in the sample application. I chose the Drools rules engine framework to define validation rules. Drools is getting more attention in capturing business rules in Java enterprise applications. Check out the article "Give Your Business Logic a Framework with Drools" for more information on using Drools for implementing business logic. I also used the Spring and Hibernate frameworks in service and data access layers, respectively, as these frameworks provide loosely coupled, flexible, and extensible solutions when working on business logic and object-relational modeling tasks. I used AspectJ (version 5.0) to define validation aspects and weave them into controller and service layers of the application. The table below lists these technologies by the application tier.
| Tier | Technology/Framework |
|---|---|
| Client | JUnit, HttpClient |
| MVC | Spring MVC |
| Service | Spring Framework |
| DataAccess | DAO |
| Rules | Drools 2.0 |
| AOP | AspectJ, AJDT 1.3 |
| Application Server | JBoss 4.0 |
| Database | Hypersonic SQL |
| IDE | Eclipse 3.1 |
Note: I used AJDT 1.3 for the latest Eclipse version (3.1). This is not a stable release. If you want to test the sample application using a stable AJDT release, use AJDT 1.2 for Eclipse 3.0. Also make sure the Eclipse project is set up with J2SE 5.0, since the application uses annotations to define aspects.
The use case for the loan processing application starts with the user entering loan details in an HTML form and submitting it, which triggers data validation on both the client and server tiers. Depending on successful data validation, a confirmation number is provided to the user that can be used for future inquiries on the loan application. These steps are shown in the sequence diagram in Figure 1.
|
|
Now, let's look at the main components of the sample application.
The listing below shows the sample code of the
onSubmit method controller class
(LoanApplicationController).
protected ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response,
Object command, BindException errors)
throws Exception {
System.out.println("Enter onSubmit().");
LoanApplication loanApp = (LoanApplication)command;
System.out.println("LoanApplication:\n"+loanApp.toString());
int confirmationNumber = service.registerLoanApplication(loanApp);
// Get opportunities
request.setAttribute("confirmationNumber",
Integer.toString(confirmationNumber));
return new ModelAndView(getSuccessView());
}
LoanApplication table in the back-end database.A code snippet of the registerLoanApplication
method in the LoanApplicationService class is shown
below.
public int registerLoanApplication(LoanApplication loanApp)
{
int confirmationNumber = 0;
try
{
dao.updateLoanApplication(loanApp);
// Get confirmation number for the submitted loan application
confirmationNumber =
dao.getLoanApplication(
loanApp.getLoanAppId()).getConfirmationNumber();
} catch (DAOException e)
{
log.error(e,e);
}
return confirmationNumber;
}
loandb database using
the Hypersonic SQL database. The database has a single table called
LoanApplication.The validation rules for the loan application fields are defined in rules XML files as shown below.
<?xml version="1.0"?>
<rule-set name="LoanApplicationRules"
xmlns="http://drools.org/rules"
xmlns:java="http://drools.org/semantics/java"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://drools.org/rules rules.xsd
http://drools.org/semantics/java java.xsd">
<!-- Java Imports -->
<java:import>java.lang.Object</java:import>
<java:import>java.lang.String</java:import>
<java:import>com.loanapp.domain.LoanApplication</java:import>
<!-- Java function to print message -->
<java:functions>
public void printMessage(
com.loanapp.domain.LoanApplication loanApp,
String msg)
{
System.out.println(msg);
}
</java:functions>
<!-- Validate SSN -->
<rule name="SSN Validation">
<parameter identifier="loanApplication">
<class>LoanApplication</class>
</parameter>
<java:condition>
loanApplication.getSsn().length() < 9
</java:condition>
<java:consequence>
printMessage(loanApplication,
"Invalid SSN - should be 9 digits.");
</java:consequence>
</rule>
<!-- Check Loan Amount -->
<rule name="LoanAmount Is High">
<!-- Parameters -->
<parameter identifier="loanApplication">
<class>LoanApplication</class>
</parameter>
<!-- Validate the data field -->
<java:condition>
loanApplication.getLoanAmount().intValue() > 1000000
</java:condition>
<java:consequence>
printMessage(loanApplication,
"LoanAmount is higher than the allowed limit.");
</java:consequence>
</rule>
</rule-set>
And the code implementation of loading these validation rules using Drools API is as follows:
public void doValidation(BaseDomainObject domainObj)
throws ValidationException
{
runValidationRules(domainObj);
}
public void runValidationRules(BaseDomainObject domainObj)
{
try
{
// Validation Rules File Name
String ruleFileName =
domainObj.getClass().getName()+"Rules.xml";
// Load the validation rules
loadRules(ruleFileName);
WorkingMemory workingMemory =
validationRules.newWorkingMemory();
workingMemory.addEventListener(
new DebugWorkingMemoryEventListener());
workingMemory.assertObject(domainObj);
// Fire rules
workingMemory.fireAllRules( );
}
catch (Exception e)
{
}
}
/**
* Load the business rules from the xml file
*/
private static void loadRules(String ruleFileName)
throws Exception
{
if (validationRules == null)
{
//Specify this resolver when we load are rules
validationRules = RuleBaseLoader.loadFromUrl(
BusinessLayer.class.getResource(
"/rules/"+ruleFileName));
}
}
The following listings show the pointcuts and advices defined to
inject validation code into the LoanApplicationController
and LoanApplicationService classes, respectively.
Here's a pointcut and around advice to intercept the
onSubmit() method and apply validation on the LoanApplication object.
@Pointcut(
"execution(* *.onSubmit(..)) && args(request, response, command, errors)"
)
void executeOnSubmit(HttpServletRequest request,
HttpServletResponse response,
Object command, BindException errors) {};
@Before("executeOnSubmit(request, response, command, errors)")
public void beforeOnSubmit(HttpServletRequest request,
HttpServletResponse response,
Object command, BindException errors) {
System.out.println("beforeOnSubmit");
}
@Around("executeOnSubmit(request, response, command, errors)")
public Object aroundOnSubmit(ProceedingJoinPoint jp,
HttpServletRequest request,
HttpServletResponse response,
Object command, BindException errors) {
Object proceedResult = null;
System.out.println("aroundOnSubmit");
try {
DataValidator validator = new DataValidator();
BaseDomainObject domainObj = (BaseDomainObject)command;
validator.doValidation(domainObj);
proceedResult = jp.proceed();
} catch (Throwable t) {
log.error(t,t);
}
return proceedResult;
}
Here's the pointcut and advice for the
registerLoanApplication() method in
LoanApplicationService class.
@Pointcut(
"execution(* *.registerLoanApplication(..)) && args(loanApp)"
)
void executeRegisterLoanApplication(LoanApplication loanApp) {};
@Before("executeRegisterLoanApplication(loanApp)")
public void beforeRegisterLoanApplication (
LoanApplication loanApp) {
System.out.println("before");
}
@Around("executeRegisterLoanApplication(loanApp)")
public int aroundRegisterLoanApplication (
JoinPoint thisJoinPoint, LoanApplication loanApp) {
System.out.println("aroundRegisterLoanApplication");
return 0;
}
Figure 2 shows the different elements of the validation framework and their relationship with each other.
|
|
I wrote two JUnit test scripts (DataValidatorTest
and LoanApplicationControllerTest) to verify that
validation is working correctly by submitting both valid and
invalid data. If validation fails, we set the
validation flag in the LoanApplication object
to false, indicating that the data cannot be sent to
the server. LoanApplicationControllerTest is a good
example, since it can be executed outside the J2EE container and the
DAO returns a test confirmation number. I also wrote a test client
(called LoanApplicationClient) using Commons
HttpClient to simulate a web request with data fields filled in
with the loan application details. Once the Submit button is
clicked, validation rules are applied to the submitted data and
validation errors are returned for any invalid data.
If basic validation passes, server-side validation rules are then applied on the data sent by the client. If there is a violation of any of the rules, server will return error messages for the invalid data.
In this article, we looked at the implementation of data validation rules using a rules engine and aspects. Data validation is an essential part of any enterprise application, and using aspects to implement it offers the flexibility to respond in an agile manner to any changes in validation requirements.
We defined data validation rules using a rules engine (Drools) and used aspects to weave validation logic in those classes where validation is required to verify the quality of user-entered data.
Since we need some kind of validation on the web client so that we don't send any invalid data past the client layer, a better solution is to use JavaScript for basic validation (such as checking empty or null values and format) and process all other types of validations (such as checking for value and business validations) using a centralized custom data validation framework.
Sometimes, validation rules are stored in a SQL database. It's a good idea to store the validation rules in memory (cache) after loading them from the database for the first time. This way, we won't be hitting the database every time we need to validate a data field.
An administration tool to add new validation rules or modify or delete existing rules (preferably a JMX-based solution) would be a nice enhancement to the proposed validation framework.
Note that both object caching and monitoring are also cross-cutting concerns that can use the help of aspects for their implementation. Check out this article on how we can implement object caching using AOP techniques.
|