Self-validaing domain objects

June 13, 2009

A common problem I encounter in enterprise systems is the messy business of validating domain objects. This all-too-common problem permeates every tier of operation, from low level persistence to high level UI, manifesting itself in multiple ways depending on operational circumstances. Operations which introduce instances of domain objects must take care to populate them appropriately before passing them on. Operations which interact with existing instances of domain objects must not only be certain that they have been appropriately populated, but must take care that they remain appropriately populated.

Here, appropriately populated simply means that properties of a domain object contain values deemed acceptable according to the specifications of the system itself. This might mean that certain properties must not be null, must contain values within a given range, and so forth.

The conventional approach to solving this problem is the introduction of a validation infrastructure. This may consist of a collection of classes each designed to inspect and accept or reject the state of a given domain object. Though this may be a common and well-understood pattern, it has some fundamental drawbacks. Foremost is that at any point in the system, a domain object's validity can only be known by inquiring its associated validation mechanism. If the validation mechanism is simply a validator class, this means calling the validate() method, passing the domain object, and reacting to the result (yes, the object is valid / no the object is invalid). Implicit in this is another major problem.

The very fact that a validation mechanism exists to accept or reject the state of a domain object implies the potential for a domain object to exist in an invalid state. It is then burdensome on the enterprise to constantly concern itself with the fact that any of its domain objects could potentially be invalid. With only the validator pattern to rely on, the enterprise must continuously interact with the validation infrastructure to keep its domain objects in order.

A potential solution to these problems is to turn the validator pattern on its head. Rather than defining a model of, and a separate validation infrastructre for domain objects, the domain objects can be made to validate themselves in real time. Properly implemented self-validation combined with appropriate error handling can result in a system in which invalid domain objects can by definition never exist. With validation overhead removed, the system can operate safe in the knowledge that its state is healthy, and with considerable overhead eliminated.

Let's look at an example of how this can be done.

In the Spring 2.5 jpetstore sample, the Account domain class is as follows:

public class Account implements Serializable {

    /* Private Fields */

    private String username;
    private String password;
    private String email;
    private String firstName;
    private String lastName;
    private String status;
    private String address1;
    private String address2;
    private String city;
    private String state;
    private String zip;
    private String country;
    private String phone;
    private String favouriteCategoryId;
    private String languagePreference;
    private boolean listOption;
    private boolean bannerOption;
    private String bannerName;

    /* JavaBeans Properties */

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    /* The rest of the getters and setters are removed for brevity. */

}

Its corresponding validator, AccountValidator, is as follows:

public class AccountValidator implements Validator {

    public boolean supports(Class clazz) {
        return Account.class.isAssignableFrom(clazz);
    }

    public void validate(Object obj, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors, "firstName", "FIRST_NAME_REQUIRED", "First name is required.");
        ValidationUtils.rejectIfEmpty(errors, "lastName", "LAST_NAME_REQUIRED", "Last name is required.");
        ValidationUtils.rejectIfEmpty(errors, "email", "EMAIL_REQUIRED", "Email address is required.");
        ValidationUtils.rejectIfEmpty(errors, "phone", "PHONE_REQUIRED", "Phone number is required.");
        ValidationUtils.rejectIfEmpty(errors, "address1", "ADDRESS_REQUIRED", "Address (1) is required.");
        ValidationUtils.rejectIfEmpty(errors, "city", "CITY_REQUIRED", "City is required.");
        ValidationUtils.rejectIfEmpty(errors, "state", "STATE_REQUIRED", "State is required.");
        ValidationUtils.rejectIfEmpty(errors, "zip", "ZIP_REQUIRED", "ZIP is required.");
        ValidationUtils.rejectIfEmpty(errors, "country", "COUNTRY_REQUIRED", "Country is required.");
    }
}

In Enterprise Java's world of POJOs, we frequently see classes, such as domain objects, consisting of nothing more than some private variables, and dumb getters and setters. What is the point of getters and setters in the first place? It is not necessarily to simply realize a one-to-one mapping of private properties to public accessors. With setters responsible for populating data, it follows that they also be responsible for populating valid data. Similarly, with getters responsible for retrieving data, it follows that they also be responsible for retrieving valid data. In both cases, when there is a problem with the validity of the data, they can exhibit specific behavior such as throwing special an InvalidDataException. The InvalidDataException lends itself to presiding over a hierarchy of data validity problems, such as unallowable null data, data outside an acceptable range, etc.

The Account class with these modifications would be as follows:

public class Account implements Serializable {

    /* Private Fields */

    private String username;
    private String password;
    private String email;
    private String firstName;
    private String lastName;
    private String status;
    private String address1;
    private String address2;
    private String city;
    private String state;
    private String zip;
    private String country;
    private String phone;
    private String favouriteCategoryId;
    private String languagePreference;
    private boolean listOption;
    private boolean bannerOption;
    private String bannerName;

    /* JavaBeans Properties */

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() throws MayNotBeEmptyException {
        if (email == null || email.trim().equals("")) {
            throw new MayNotBeEmptyException("email");
        } else {
            return email;
        }
    }

    public void setEmail(String email) throws MayNotBeEmptyException {
        if (email == null || email.trim().equals("")) {
            throw new MayNotBeEmptyException("email");
        } else {
            this.email = email;
        }
    }

    /* The rest of the getters and setters are removed for brevity. */

}

A potential issue with this model lies with initialization of domain objects. When a domain object is instantiated, it will most likely be in an invalid state as none of its properties have yet been set. Furthermore, even after some properties have been set, a domain object may not be considered valid in its entirety until certain specific properties have been appropriately set.

This issue may be addressed in any number of ways, such as implementing an interface similar to Spring's InitalizingBean or FactoryBean, in which cases the domain object would have to go through its afterPropertiesSet() or getObject() methods, respectively, before it could be ingested by the rest of the system. Another approach is to introduce a simple Initializable interface to be implemented by all domain objects, which would add the initialize() method. This would be called after an object is initialized, and would exhibit the same exception behavior as the modified getters and setters of the domain object.

The Account class with this modification would be as follows:

public class Account implements Serializable, Initializable {

    /* Private Fields */

    private String username;
    private String password;
    private String email;
    private String firstName;
    private String lastName;
    private String status;
    private String address1;
    private String address2;
    private String city;
    private String state;
    private String zip;
    private String country;
    private String phone;
    private String favouriteCategoryId;
    private String languagePreference;
    private boolean listOption;
    private boolean bannerOption;
    private String bannerName;

    public void initialize() throws InvalidDataException {
        if (firstName == null || firstName.trim().equals("")) {
            throw new MayNotBeEmptyException("firstName");
        }
        if (lastName == null || lastName.trim().equals("")) {
            throw new MayNotBeEmptyException("lastName");
        }
        if (email == null || email.trim().equals("")) {
            throw new MayNotBeEmptyException("email");
        }
        if (phone == null || phone.trim().equals("")) {
            throw new MayNotBeEmptyException("phone");
        }
        if (address1 == null || address1.trim().equals("")) {
            throw new MayNotBeEmptyException("address1");
        }
        if (city == null || city.trim().equals("")) {
            throw new MayNotBeEmptyException("city");
        }
        if (state == null || state.trim().equals("")) {
            throw new MayNotBeEmptyException("state");
        }
        if (zip == null || zip.trim().equals("")) {
            throw new MayNotBeEmptyException("zip");
        }
        if (country == null || country.trim().equals("")) {
            throw new MayNotBeEmptyException("country");
        }
    }


    /* JavaBeans Properties */

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() throws MayNotBeEmptyException {
        if (email == null || email.trim().equals("")) {
            throw new MayNotBeEmptyException("email");
        } else {
            return email;
        }
    }

    public void setEmail(String email) throws MayNotBeEmptyException {
        if (email == null || email.trim().equals("")) {
            throw new MayNotBeEmptyException("email");
        } else {
            this.email = email;
        }
    }

    /* The rest of the getters and setters are removed for brevity. */

}

This concept is of course highly theoretical and potentially contentious, and I have not yet seen or used it in an enterprise system. It is a provocative idea, and if nothing else might prove interesting to play with.