Back to Basics: Validation

Dizingov Cafe, Elsternwick, Melbourne, Australia, 2006-01-29 21:22 +1100

#software_engineering

Contents

  • Introduction
  • Competing Requirements
  • Validator Objects
  • Relational Validation
  • Alternative Approaches

Introduction

In this article I’ll take a look at the general requirements that every system has in terms of validation and put forth a model that addresses all those requirements.

The model presented in this article is based on the x1seven Validation library which is available as open source under the GNU LGPL license. More information on that library can be found at the Elements / Validator projekt website. The library can be downloaded form SourceForge. The source code is also available on SourceForge.

Competing Requirements

Constructing validation code is not necessarily as straightforward as you might think: we must put some thought into balancing user experience against architecture, performance and an appropriate level of defensiveness to allow our application to be called other than through the UI. Investing that effort in planning means that once you start writing code, deciding where to implement a given validation rule becomes a no-brainer.

Usability. In every webapp we need to validate the input supplied by the user. Our goal when our user submits a form load of data to us should be to identify all of the problems in that data in one go, and give them a complete list of things to fix. We certainly don’t want to make our user click the submit button on our form once for each field that needs validating, as this would likely turn them off using our app.

Defensiveness. Most apps with web front-ends will also be accessed in other ways. How many webapps have you written that didn’t at least have a “public” and an “admin” web UI? Or have some batch process that was cron’d to spin up at 3am? Some apps may also be called through graphical UI’s (e.g. Swing) or as web services.

Validating in the UI tier enables us to get a quick response back to the user, but we also need to do a defensive validation check in the “back-end” to cater for our application being called through some other channel. Adding validation at these different points shouldn’t require us to re-implement the validation rules each time. We need to come up with a way to make our validation code re-usable.

Architecture. In EJB applications the user interface tier is normally separated from the database by an RMI boundary, so grabbing data from the database to do detailed business logic validation in the UI tier becomes an expensive proposition.

Validator objects

Centralized Validation

Let’s start by centralizing our validation code to Validator objects. These will be stateless Java Beans that provide validate() methods for validating our model data. There’s nothing particularly special about the code that goes inside those methods: it’s probably the same code you would write in your Struts Action’s or your EJB stateless session beans.

The following diagram gives an example of how Validator Objects can fit into two common architectures: Struts and EJB applications.

Now we can invoke our validation code from our UI tier to present error messages directly to the user, and also defensively from just in front of the appropriate back-end tier such as our business or DAO tier. This means that we’re performing the validation a second redundant time, however the cost of performing validation is typically very low, especially when compared to the other things we’re probably doing such as object relational mapping via Hibernate or EJB. The cost of a testing a given validation rule should be evaluated on a case-by-case basis though.

Communicating validation errors back

When a Validator generates validation errors, how are we best to communicate those back to the caller? We could return a String array or some other construct containing error messages. This would work fine in the UI tier, but in the lower layers we often need to return values from business methods. For example, an Add method in a DAO will probably need to return the surrogate key of the newly inserted row.

A better alternative is to make all our validate() methods return void, and throw an exception when one or more validation errors are detected. Most exceptions just contain a single message, but this is where we can address the usability requirements by building an exception class that contains multiple messages.

The Elements / Validation Library

Classes in the Elements / Validation library:

Type Description
Message Represents a single validation error message generated by a validate() method on a Validator object.
Messages Collection of Message objects generated by validate() methods
ValidationException Indicates that one or more rules failed during validation. For the actual failure messages, retrieve the Messages object using the getMessages() method.
ActionMessagesConverter Converts a Messages collection to the equivalent Struts ActionMessages collection.

It turns out that this ValidationException can be made to be generic. It needs to be able to be able to hold multiple messages, and replacement values to go with those messages. In fact it really mirrors the design of the ActionMessages / ActionMessage structure in Struts. This is the basis of the x1seven Validation library which provides ValidationException, Messages and Message classes.

For Struts applications, the x1seven Validation library also provides the ActionMessagesConverter, which takes one of our generic Messages objects and translates it to the equivalent Struts ActionMessages instance. This structure allows you to raise multiple validation errors from your business tier without being bound to Struts, but still use Struts’ capabilities to render those messages when you get back to the UI tier.

Putting it all together

Putting these parts together now, here’s an example of what our WidgetValidator might look like:

package myapp;

import com.x1seven.hydrogen.validation.Message;
import com.x1seven.hydrogen.validation.Messages;
import com.x1seven.hydrogen.validation.ValidationException;

/**
 * <p>
 * Provides validation services to check {@link Widget}
 * objects and Widget data.
 * </p>
 *
 * @author                              Brendon Matheson
 * @version                             $Revision: 1.7 $
 * @since                               1.0.0
 */
public class WidgetValidator extends Object {

    public static final String PROPERTY_WIDTH = "width";
    public static final String PROPERTY_LENGTH = "length";
    public static final String PROPERTY_HEIGHT = "height";

    public static final double MAX_LENGTH = 10;
    public static final double MAX_WIDTH = 10;
    public static final double MAX_HEIGHT = 10;
    public static final double MAX_VOLUME = 1000;
		

    /**
     * <p>
     * Validates the supplied widget.  Internally this
     * method delegates to
     * {@link #validate(double, double, double}.
     * </p>
    */
    public void validate(Widget widget) throws ValidationException
    {
        if(widget == null)
        {
            throw new IllegalArgumentException("widget cannot be null");
        }

        validate(
            widget.getLength(),
            widget.getWidth(),
            widget.getHeight());
    }

    /**
     * <p>
     * Validates the supplied {@link Widget} details.
     * </p>
     *
     * @param       length              the length of the
     *                                  Widget
     * @param       width               the width of the
     *                                  Widget
     * @param       height              the height of the
     *                                  Widget
     * @throws      ValidationException if one or more
     *                                  validation errors
     *                                  are found
     */
    public void validate(
        double length,
        double width,
        double height) throws
            ValidationException
    {
        Messages messages = new Messages();

        this.validate(
            messages,
            length,
            width,
            height);
    }

    /**
     * <p>
     * Validates the supplied {@link Widget} details
     * and adds any validation messages generated to the
     * supplied {@link Messages} object before throwing a
     * ValidationException.
     * </p>
     *
     * @param       length              the length of the
     *                                  Widget
     * @param       width               the width of the
     *                                  Widget
     * @param       height              the height of the
     *                                  Widget
     * @throws      ValidationException if one or more
     *                                  validation errors
     *                                  are found
     */
    public void validate(
        Messages messages,
        double length,
        double width,
        double height) throws
            ValidationException
    {
        if(messages == null)
        {
            throw new IllegalArgumentException("messages cannot be null");
        }

        if(length > MAX_LENGTH)
        {
            messages.add(
                PROPERTY_LENGTH,
                new Message(
                    MessageConstants.WIDGET_EXCEEDS_MAXIMUM_ALLOWED_LENGTH,
                    new Double(MAX_LENGTH)));
        }

        if(width > MAX_WIDTH)
        {
            messages.add(
                PROPERTY_WIDTH,
                new Message(
                    MessageConstants.WIDGET_EXCEEDS_MAXIMUM_ALLOWED_WIDTH,
                    new Double(MAX_WIDTH)));
        }

        if(height > MAX_HEIGHT)
        {
            messages.add(
                PROPERTY_HEIGHT,
                new Message(
                    MessageConstants.WIDGET_EXCEEDS_MAXIMUM_ALLOWED_HEIGHT,
                    new Double(MAX_HEIGHT)));
        }

        if((length * width * height) > MAX_VOLUME)
        {
            messages.add(
                Messages.GLOBAL_MESSAGE,
                new Message(
                    MessageConstants.WIDGET_EXCEEDS_MAXIMUM_ALLOWED_VOLUME,
                    new Double(MAX_VOLUME)));
        }

        // If any error messages were found during
        // validation, build and throw the exception
        if(messages.hasMessages())
        {
            throw new ValidationException(messages);
        }
    }
}

Note that by splitting the Messages object out from the ValidationException itself, we are able to defer creating the exception instance until we are sure we need it. This is important because instantiating exceptions is quite expensive due to the amount of context information such as the stack trace that the JVM must marshall.

Relational Validation

What we mean by this is validation of data that’s being supplied by the user against data that already exists in in the database. This type of validation is more complex, and because of the need to go and query the database, it is much more expensive than basic validation.

Simple application of the Validator Object pattern to relational validation:

Using our Validator Object approach as it stands, we would have a validation method in the validator object that would call into our business logic or DAO layer to get access to the data necessary to validate the user input. For non-EJB webapps (e.g. Servlet, Struts, Web Works etc) where all the business logic is in the Servlet container, this is fine. In an EJB application however the effect on performance could be disastrous the Validator Object in our web tier started pulling data across the RMI boundary, particularly when the web tier and the EJB tier are on different physical machines.

One way to tackle this could be to defer performing relational validation until we get to the back-end, but this would put us back into a situation where the user has to click submit multiple times to get all of the relevant error messages. This may be acceptable for some apps, however if we want to provide the smoothest user experience possible we’ll need to be a bit smarter.

Smarter application of the Validator Object pattern to relational validation:

Where RMI calls come into play, we really need to split our validation rules and our validator objects into two parts: relational and non-relational. The non-relational validation continues to be invoked directly both at the user interface layer, and also defensively just in front of our business logic. The non-relational validation is physically located on the remote side of the RMI call, and our validator object must call it via RMI from the user interface layer, or directly when it is being called defensively just in front of the business logic.

Object-Oriented Approach

Introduction

Many people will be shaking their heads at this point, and rightly pointing out that splitting the validation code out of the object is not strictly adhering to Object-Oriented principles. They would say that our model objects should have validation built directly into them with, say, an isValid() method. This way we make validation into a behaviour of our model objects as opposed to an external service. This is a perfectly reasonable suggestion, but how does it tally with the issues of useability, architecture and performance that we’ve discussed above?

User Input Validation

Validation in a pure Object-Oriented context:

When a user fills out a form and submits it to our application, the data is supplied as strings. This is where we hit the first snag in our purely Object-Oriented model. We shouldn’t be supplying numerical, date or other non-string values to our model objects as strings, so data type validation doesn’t fit into the O.O. model. Our only option is to perform the datatype validation before we set the values into our model object. By doing this we again go back to a situation where our validation is partitioned into two parts (datatype validation, then the rest of the validation) and consequently our user will get validation messages in two batches.

Relational Validation

When we get up to performing our relational validation using an Object-Oriented isValid() method, we now find ourselves calling into our data access layer from our model object to get the data we need for that validation. Most software engineers would be happy for a model object to be aware of it’s relationship with another model object, but would baulk at the idea of model objects directly communicating with the data access layer.

Also, in an EJB application, the isValid() methods on our model objects will ncessarily have to pull data across RMI to perform relational validation in our user interface tier.

On the face value a pure Object-Oriented approach might seem attractive, but when we look into it in detail we find it lacking both in terms of useability and architecture. The reason for this is that full validation is rarely just of an object’s internal state. It must also include validation of the data before we provide it to our object, and also of the object in the context of it’s external relationships.

Conclusion

  • Moving our validation logic into a Validator service object allows us to apply the same validation rules from the user interface tier and defensively from in front of our business logic.
  • Where we need to do relational validation in a distributed environment, our validation will be made more efficient by distributing it as well.
  • The x1seven Validation library can be used for all types of validation to communicate multiple messages back to the user.
  • Due to the preliminary checking and need check external related data, a pure Object-Oriented model does not fit with the requirements we typically encounter when constructing validation code.