Implementing validation using validity scopes

Introduction

In a typical business web applications there are following common options for validating business rules:

  • Validator, usually in presentation layer which checks rules related to single UI form or Presentation Model object
  • Service in domain layer which validates input parameters of its business methods
  • Entity or Value Object in domain layer which validates changes made to its state

All of these have their benefits and tradeoffs hence it usually doesn't make sense to apply only one strategy for all types of rules. One possibility for establishing common validation guidelines for the application is to use the concept of validity scope.

Following scopes exist:

  • single field scope - we don't need to check any other fields of this object in order to make sure that value of given field is valid. E.g password must be at least 8 characters long, e-mail address must have specific format.
  • object scope - field's validity depends on the values of one or more other fields of this object. E.g new password and confirmation password must be equal.
  • global scope - we need to compare one or more fields of given object with other (possibly persistent) objects in the system to check the validity. E.g username must be unique, meeting start and end time must not overlap with any other existing meeting.

Validation in single field scope

This is the simplest type of validation which usually isn't very interesting from the perspective of particular business domain. Therefor it makes sense to use separate Validator object for these kinds of rules.

There are many different strategies for implementing the Validator and most web frameworks provide support for this kind of validation. In addition there are some validation libraries like Apache Commons Validator or Hibernate Validator which can be integrated with any application framework.
For example Hibernate Validator can be used together with Spring MVC using following adapter:

public class HibernateValidatorAdapter implements Validator {
  private Class beanClass;

  public HibernateValidatorAdapter(Class beanClass) {
    this.beanClass = beanClass;
  }

  public boolean supports(Class arg0) {
    return beanClass.equals(arg0);
  }

  public void validate(Object bean, Errors errors) {
    ClassValidator validator = new ClassValidator(beanClass);
    InvalidValue[] invalids = validator.getInvalidValues(bean);

    for (InvalidValue value : invalids) {
      errors.rejectValue(value.getPropertyPath(), value.getMessage());
    }
  }
}

Then for checking some rule all we need to do is to add annotations to the validated field or its getter. This is especially handy if we are using Exposed Domain Model because then we can annotate our domain model classes. If direct access to Domain Model is not possible we may need to annotate DTOs or Presentation Model classes instead. However the drawback then is that some annotations may have to be duplicated.

Validation in single object scope

In most cases this kind of validation should be the responsibility of domain Entity or Value Object. If we decided to place such logic somewhere else we would be effectively moving towards Anemic Domain Model.

Where should this validation be executed in Entities? Clearly we cannot check such rules in setters because then the behavior of setters will depend on the order in which they are called. Alternatively we can add special "validate" method which must be called before any setter. This has however the drawback that it's not possible to enforce clients to call "validate" every time. Better solution is to use special update method (or constructor in case of Value Objects) that enable modifying all related fields as single atomic operation.

For example we have to implement user story "User wants to change time of scheduled meeting" in a classical web application. Meeting start and end time can be implemented using Interval Value Object which enforces the rule that start time is before end time.

public class Meeting {
  private Interval interval;
  public void changeTime(Date start, Date end) {
    try {
      interval = new Interval(start.getTime(), end.getTime());
    } catch (IllegalArgumentException e) {
      throw new BusinessException("Invalid meeting time " + ...);
    }
  }
}

Validation in global scope

In order to check these rules we need to have access to persistence layer. Ideally Entities should be able to validate all complex rules concerning themselves. However whether allowing Entities to access Repositories is good practice or not is a matter of some dispute. So in general it seems that domain Services are better for such checks.

public class MeetingService {
  public void rescheduleMeeting(Meeting meeting) {
    if (meetingRepository.overlapsWithSomeOtherMeeting(meeting)) {
      throw new BusinessException(...);
    }
    meetingRepository.save(meeting);
    ...
  }
}

Interestingly one of my co-workers Timur has found a way how to implement global scope uniqueness validation rules using the "Validator with annotations" approach see his post Spring MVC and Hibernate

Labels

validation validation Delete
design design Delete
hibernate hibernate Delete
mvc mvc Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Apr 14, 2009

    Anonymous says:

    The problem with implementing validation in a service is that you need to explic...

    The problem with implementing validation in a service is that you need to explicitly remember to call it. Validations are there to enforce constraints on the entities, and should be validated when ever a transaction ends, not only when a service call ends. Some times, different services will participate in a transaction, an an entity (or entities) may be have an inconsistent state until the moment when the final service call is made, if you validate before that, things will fail. That is why the validation rules should be part of the entity, but the moment when they are called should be controlled transactionally.

    It is also important to remember that validation logic can be different when inserting, updating or deleteing records, so you validation API should be able to distinguish between this operations (for example, you can insert a new customer with no debts in the system, but you can not delete a customer that still has a debt with you).