Friday, December 27, 2013

Design patterns in validation

    Validation is a pretty common business requirement. We need to be sure that the input data is valid and remember that: junk in -> junk out. Let's say that we want to validate a request object. The easiest way is to produce a big chunk of IF statements. Do you like that IF ... IF ... IF ... blocks one after another? I hate them. So how to deal with validation?
First of all we should try to utilize JSR-303 (Bean Validation). There is a set of annotations (e.g. @NotNull, @Size(...), @Min(...), @Max(...)) that can be placed on request object fields and validation can be performed behind the scenes. However, sometimes it is not enough. Instead of producing IF after IF after IF we can break validation into small portions. Let's assume that we want to validate a simple string object and we want to be sure that the string does not start with 'wrong' and does not end with 'bad'. How about the following code:

import com.google.common.base.Optional;
 
public abstract class BaseValidator<K> {

    private Optional<BaseValidator<K>> nextValidator = Optional.absent();

    public final void validate(K toValidate, ValidationResult validationResult) {
        validateSepcific(toValidate, validationResult);
        if (nextValidator.isPresent()) {
            nextValidator.get().validate(toValidate, validationResult);           
        }
    }
    
    public BaseValidator<K> and(BaseValidator<K> nextValidator) {
        this.nextValidator = Optional.of(nextValidator);
        return this;
    }

    protected abstract void validateSepcific(K toValidate, ValidationResult validationResult);
}
 
public class ValidationResult {
	public List<String> violations = new ArrayList<>();
	
	public void addViolation(String violation) {
		violations.add(violation);
	}
	
	public boolean isValid() {
		return violations.isEmpty();
	}
	
	@Override
	public String toString() {
		return violations.toString();
	}
}
 
public class ValidateStartsWithWrong extends BaseValidator<String> {
    
    @Override
    protected void validateSepcific(String toValidate, ValidationResult validationResult) {
        if (toValidate.startsWith("wrong")) {
            validationResult.addViolation("STARTS_WITH_WRONG");
        }
    }
}

public class ValidateEndsWithBad extends BaseValidator<String> {

    @Override
    protected void validateSepcific(String toValidate, ValidationResult validationResult) {
        if (toValidate.endsWith("bad")) {
            validationResult.addViolation("ENDS_WITH_BAD");
        }
    }
}
 
We have building blocks in place so let's compose our validation service.
public class ValidationService {
    
    public ValidationResult validate(String toValidate) {
        ValidationResult result = new ValidationResult();
        getValidationChain().validate(toValidate, result);
        return result;
    }
    
    private BaseValidator<String> getValidationChain() {
        return new ValidateStartsWithWrong().and(new ValidateEndsWithBad());
    }
}
Now, we can use our service:
 public class ValidationExample {
   
    public static void main(String[] args) {
        ValidationService service = new ValidationService();
        ValidationResult result1 = service.validate("wrong and bad");
        ValidationResult result2 = service.validate("perfectly fine");
        System.out.println("Is valid: " + result1.isValid() + " " + result1);
        System.out.println("Is valid: " + result2.isValid() + " " + result2);
    }
}

The output is:
Is valid: false [STARTS_WITH_WRONG, ENDS_WITH_BAD]
Is valid: true []

This approach may result in a class number explosion. However, I do not see anything wrong there. Thanks to that we can have a whole bunch of small and focused classes - high cohesion and SRP principles are followed. Our unit tests will be much smaller as well. What is more we can compose our validators and reuse them in different chains - DRY/DIE principle. When we want to add more validation rules we can add more validators (Open-Closed principle). Presented validators are extremely simple, however the reality is different and real life business rules for validation are usually much more complicated, e.g. they might involve communication with external web service or database. With presented approach we are ready for them.
It was achieved with Template Method (validateSpecific method in BaseValidator) and Chain of Resposibility (in ValidationService) design patterns.

P.S. This is the last post in 2013. Happy New Year and stay tuned for the upcoming entries in 2014!

No comments :

Post a Comment