Submarino.com.br




Criando sua API de validação

Proposta

Neste workshop veremos como criar, passo a passo, sua própria API de validação. Para isso definiremos primeiro as propriedades de uma API deste tipo e utilizaremos o padrão Composite Object para aumentar a reutilização da API.

O que é Validar ?

Validar significa testar que algo é o que deveria ser. Normalmente significa verificar que um dado valor é diferente de algum valor fixo ou está em um intervalo de valores.

O resultado de uma validação não é a apenas a indicação de que o valor está valido ou não, mas principalmente a razão de porquê não está válido. Isto significa que nossos validadores não podem simplesmente retornar "sim" ou "não" mas terão que retornar um objeto que informe porque o valor é invalido. Se for válido, obviamente não precisamos informar mais nada.

O modelo

O nosso modelo é bem simples. Definimos um validador como uma interface a ser implementada conforme a lógica de validação. O validador têm um método validate que recebe o objeto a ser validado e retorna o resultado da validação no objeto ValidationResult.

Faremos o validador aceitar um objeto genérico qualquer fortemente tipados. Veremos depois a utilidade desta funcionalidade e o porquê de não usar Object directamente.

public interface Validator<T> {
		public ValidationResult validate(T object);	
}
			
Código 1: Interface Validator

Como vimos, apenas precisamos de informar as razões de porquê o valor não é válido. Precisamos apenas acrescentar um objeto InvalidationReason ao resultado, tornando o objeto ValidationResult uma coleção de InvalidationReason

public class ValidationResult implements Iterable<InvalidationReason> {
	private final List<InvalidationReason> reasons = new LinkedList<InvalidationReason>();

	public Iterator<InvalidationReason> getIterator(){
	    return reasons.iterator();
	}
	
	public void addReason(InvalidationReason reason){
	  reasons.add(reason);
	}
	
	public void removeReason(InvalidationReason reason){
	  reasons.remove(reason);
	}
}			
			
Código 2: Classe ValidationResult

Contudo, apenas a razão não é suficiente. Imaginemos uma situação em que o validador não consegue decidir se o valor é válido ou não. Por exemplo, um validador de email tenta connectar-se a um servidor DNS para verificar que o dominio do email existe. Se o validador não poder fazer essa ligação, ele não pode dizer que o email é válido, mas também não pode dizer que não é. Nesta circunstância o validador precisa informar a duvida. A forma de fazer isso é definir um grau de severidade para a razão de invalidação.

public interface InvalidationReason {

      public InvalidationSeverity getSeverity();
      public String getMessage();
      public Object[] getParams();
}			
			
Código 3: Interface InvalidationReason
public enum InvalidationSeverity {
	SEVERE,
	WARNING
}			
			
Código 4: Enumeração InvalidationSeverity

Definimos InvalidationReason como uma interface para termos o máximo de flexibilidade na implementação. O método getMessage() retorna uma mensagem e um método getParams os parametros da mensagem. Isto serve para podermos criar mensagem corretamente internacionalizadas e deixa a UI se preocupar com o texto real e sua apresentação.

Faltou apenas decidir como sabemos que o valor está válido ou não. Sabemos que ele não está válido se existir uma razão severa para a invalidação. Então, em vez deverificarmos isso a todo o momento, vamos criar um método, isValid(), que faz esse trabalho.

public class ValidationResult implements Iterable<InvalidationReason> {
	private final List<InvalidationReason> reasons = new LinkedList<InvalidationReason>();

	// o mesmo codigo de antes
	
	public boolean isValid(){
		for (InvalidationReason reason : reasons){
			if (reason.getSeverity().equals(InvalidationSeverity.SEVERE){
			  return false;
			}
		}
	   return true; 
	}
}
			
Código 5: Adicionando isValid

Este método obriga a iterar todas as razões para verificar se existe alguma com severidade SEVERE. Não é muito eficaz. Se tivessemos um Map seria bem mais facil...