Validação de atributos - Melhores práticas!

Galera.

Vamos supor que temos a seguinte aplicação em JavaEE.

public class Livro{

	@NotEmpty
	private String nome;
	@NotEmpty
	private double valor;

}
public class LivroDAO{

	public void salvar(Livro livro){
		executa o insert no banco
	}

}
public class Cadastro extends HttpServlet{

	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

		Livro livro = new Livro(req.getParameter("nome"),req.getParameter("valor"));
		ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
		Validator validator = factory.getValidator();
		Set<ConstraintViolation<Livro>> violations = validator.validate(livro);
		if(violations.size() <= 0){
			new LivroDAO.salvar(livro);
		}
	}

}

Até ai legal… é o padrão de qualquer aplicação.
Porém, tenho a seguinte dúvida:

A validação é feita no servlet, e por este motivo, um outro programador pode realizar o seguinte código:

new UserDAO.save(new Livro("",90));

Supondo que o banco de dados não valide nada (apenas seja responsabilidade da aplicação java), essa abordagem não me garante a integridade da aplicação, no caso o @NotEmpty private String nome.

Existe alguma forma de colocar a validação no processo final de criação do usuario, como no UserDAo.salva, por exemplo???

Existe, no metodo salvar faça assim

if(user.getNome() == null || user.getNome().isEmpty()){
//dispara exececao
}

mas acho que existe alguma anotacao para tamanho minimo, ai voce pode coloca o tamanho minimo de 1 por exemplo forçando a propriedade a ter valor…

Por favor, sempre que for postar código, o poste entre as tags [code][ /code].

Leia nosso ‘How To’ antes de postar: http://www.guj.com.br/java/287476-gujnautas-how-to

Ok. Obrigado pela dica! Já corrigi o tópico.

Rodrigo Sasaki, obrigado pela resposta!

Na verdade corrigi o exemplo. O certo é o @NotEmpty.
Porém a minha duvida é o local que a validação é feita.

No exemplo acima (utilizado pela maioria dos sistemas, segundo minha experiencia profissinal), a validação foi feito no servlet e isso realmente funciona. Mas se um novo programador instancia a classe UserDAO e passa qualquer tipo de usuario, a classe irá realizar a operação no banco. Apesar das regras de validações estarem no model (com as anotações), a execução da validação ocorre apenas no Servlet, o que permite que um novo desenvolvedor faça o seguinte:

new UserDAO.save(new Livro("",90)); 

Como centralizar o processo de criação de usuário, de forma que ninguém consiga enviar uma ordem de criação sem que ela seja validada??

[quote=CodeDeveloper]Galera.

Vamos supor que temos a seguinte aplicação em JavaEE.

public class Livro{

	@NotEmpty
	private String nome;
	@NotEmpty
	private double valor;

}
public class LivroDAO{

	public void salvar(Livro livro){
		executa o insert no banco
	}

}
public class Cadastro extends HttpServlet{

	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

		Livro livro = new Livro(req.getParameter("nome"),req.getParameter("valor"));
		ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
		Validator validator = factory.getValidator();
		Set<ConstraintViolation<Livro>> violations = validator.validate(livro);
		if(violations.size() <= 0){
			new LivroDAO.salvar(livro);
		}
	}

}

Até ai legal… é o padrão de qualquer aplicação.
Porém, tenho a seguinte dúvida:

A validação é feita no servlet, e por este motivo, um outro programador pode realizar o seguinte código:

new UserDAO.save(new Livro("",90));

Supondo que o banco de dados não valide nada (apenas seja responsabilidade da aplicação java), essa abordagem não me garante a integridade da aplicação, no caso o @NotNull private String nome.

Existe alguma forma de colocar a validação no processo final de criação do usuario, como no UserDAo.salva, por exemplo???

[/quote]

Acredito que o voce quer nao tem como ser feito, quanto entrar alguem novo na sua equipe voce deve lhe mostrar o jeito certo de se fazer as coisas e acompanhar o caboblo no começo, e antes dele fazer comits no projeto refiçar junto com ele o que ele fez e se tiver algo errado mostrar explicar porque esta errado e mostrar o jeito certo, mas encapsulara para fazer so pela servlet so se voce fazer deuso da orientação a objetos e jogar a logica da persistencia dentro da servlet…

Mas Cristian,

A orientação a objetos tem o objetivo de sempre alcançar o reuso. O correto não seria deixar a lógica de validação junto com a de inserção no Banco de Dados??
Digo como uma espécia de método:

public class UserFacade{
	public Validator criar(Livro livro){

		ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
		Validator validator = factory.getValidator();
		Set<ConstraintViolation<Livro>> violations = validator.validate(livro);
		if(violations.size() <= 0){
			new LivroDAO.salvar(livro);
		}
		return validator;
	}
}

Desta forma, um novo devenvolvedor chamaria a UserFacade.criar e garantiria a consistencia dos dados em qualquer parte do sistema que essa função fosse chamada. E caso a validação não fosse satisfeita, o método ainda retornaria um Validator com os erros de validação.
A abordagem está correta?

com uma pergunta eu quebro essa sua teoria e ai vai ela:

o que voce fara para o programador naum chamar diretamente o LivroDao e sim a classe UserFacade ?

se voce garantir que ninguem conseguira instanciar o LivroDAO e chama-lo diretamente isso funcionara perfeitamente, naum fecho outra falha, mas se a classe LivroDAO poder ser instanciada de outros lugares do sistema e naum apenas de dentro do Facade seu esquema esta furado…

quote=CodeDeveloper

public class Cadastro extends HttpServlet{

	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

		Livro livro = new Livro(req.getParameter("nome"),req.getParameter("valor"));
		ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
		Validator validator = factory.getValidator();
		Set<ConstraintViolation<Livro>> violations = validator.validate(livro);
		if(violations.size() <= 0){
			new LivroDAO.salvar(livro);
		}
	}

}

Até ai legal… é o padrão de qualquer aplicação.
[/quote]

Sobre como fazer sua propria API de validação. É um pouco mais profundo do que parece.

[quote]
Porém, tenho a seguinte dúvida:

A validação é feita no servlet, e por este motivo, um outro programador pode realizar o seguinte código:

new UserDAO.save(new Livro("",90));

Supondo que o banco de dados não valide nada (apenas seja responsabilidade da aplicação java), essa abordagem não me garante a integridade da aplicação, no caso o @NotEmpty private String nome.

Existe alguma forma de colocar a validação no processo final de criação do usuario, como no UserDAo.salva, por exemplo???[/quote]

A validação pode ser colocada onde vc quiser. Aliás, em tese, sempre que vc muda de camada, vc deveria fazer a validação. O que acontece é que existem validações de diferentes tipos.
Na camada de apresentação ( o servlet) vc valida se o input é válido. Ou seja, se o usuário não está tentando driblar alguma regra (por exemplo colocar uma data que não existe), se preencheu tudo o que deveria, etc…
É é a validação básica.

Depois vc valida no serviço. Aqui é uma validação mais de negocio. Se a informação não é repetida, se é permitido, se faz sentido de negocio, etc… estas são validações mais especificas e mais próximas ao negócio.

Depois, se vc quiser vc pode validar na camada de persistência também. É um bocado trivial pq vc só vai validar se não está vazio etc… mas em tese as outras camadas já fizeram isso. É apenas uma segurança a mais.
É bom vc por validação em todas as camadas, exactamente para escapar desse cenário que colocou que “o programador pode fazer asssim e estragar tudo”, mas depende da robustez que o sistema precisa nem sempre é necessário. A regra geral é : quanto mais publico, mais precisa de validação.

Utilizar uma api como a do hibernate validation ajudam vc apenas na camada de persistência. Nas outras as regras são normalmente especificas e dependentes do negocio. É claro que vc pode usar api de validação (Beans Validation, por exemplo), ha muitas por ai, ou vc pode fazer a sua como indiquei - não é difícil. O ponto é que vc use uma api de validação. Compartimentalize e isole bem as regras , use composição de validadores para não reimplementar a mesma regras duas vezes e se possível use annotations. Mas seja consciente que annotations não funciona em todos os casos, então tenha uma forma mais programática sempre à mão.

Validação é muito importante e não é tratada com o devido respeito. Validação é uma parte do domínio e muito importante no modelo de desenvolvimento orientado ao domínio.

Agradeço a interação de todos!

Cristian,

Um método para não deixar o desenvolvedor chamar diretamente o “UserDAO.salva”, seria deixar a classe abstrata, e então fazera herança com o UserFacade e utilizar o método apenas dentro da Facade. Porém, vejo que isso não faz muito sentido, pois desta forma iria dizer que UserFacade é filha de UserDAO, o que não é o correto, porém, resolveria o prolema de segurança. Agora se você me pergunta: E se o desenvolvedor fizesse outra classe qe herdasse o UserDAO e chamasse o método criar de dentro dela. Nesta caso não teria resposta…rsrs

sergio,

Concordo plenamento com o que você disse: “Validação é muito importante e não é tratada com o devido respeito”. E é exatamente po isso que estou querendo saber mais sobre melhores pratocas, pois nas empresas que trabalhei já encontrei muitos furos como este que exemplifiquei.

O que você sujere é realizar a validação em todas as camadas necessárias, mesmo que pra isso eu tenha que duplicar código de válidação? Esta abordagem não causaria um grande problema no caso da regra mudar, como por exemplo, ao invés do campo não poder ser nulo, ele passar a aceitar apenas caracteres com um número minimo de 4. Desta forma eu teria que alterar todas as partes da validação que antes validavam o not null, para validar agora o mínimo de 4 caracteres?

[quote=CodeDeveloper]
sergio,

Concordo plenamento com o que você disse: “Validação é muito importante e não é tratada com o devido respeito”. E é exatamente po isso que estou querendo saber mais sobre melhores pratocas, pois nas empresas que trabalhei já encontrei muitos furos como este que exemplifiquei.

O que você sujere é realizar a validação em todas as camadas necessárias, mesmo que pra isso eu tenha que duplicar código de válidação? Esta abordagem não causaria um grande problema no caso da regra mudar, como por exemplo, ao invés do campo não poder ser nulo, ele passar a aceitar apenas caracteres com um número minimo de 4. Desta forma eu teria que alterar todas as partes da validação que antes validavam o not null, para validar agora o mínimo de 4 caracteres?[/quote]

Acho que vc passou batido quando eu falei de usar composição. Não vc não replica o codigo. Vc cria um validador e o usa várias vezes. Se o codigo mudar, vai mudar só dentro do validador.
A composição de validadores (veja no link que eu mostro como se faz) é a sua arma para não repetir definição de regras, mas repetir o uso da regra.

sergio,

Realmente você citou a composição, desculpe-me. Analisei o link passado e acho entendi o que você quis dizer.
Veja se a abordagem está correta, por favor:

De modo geral, a idéia é ter uma classe que valide Livro, centralizando as regras de validações e utilizando um validador.

public class LivroValidador{

	public boolean isValid(Livro livro){
		// Utilizar aqui o Validar e centralizar as regras de validação
	}



}

Então, utilizar essa classe em todos os pontos necessários para garantir a persistencia, como por exemplo em LivroDAO, CadastroController e etc.

public class CadastroController extends HttpServlet{  
      
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
      
		Livro livro = new Livro(req.getParameter("nome"),req.getParameter("valor"));
		// Utilização da classe validadora  
		if(new LivroValidador.isValid(livro)){  
			new LivroDAO.salvar(livro);  
 		}  
        }  
      
}
public class LivroDAO{  
      
	public void salvar(Livro livro){ 
		// Utilização da classe validadora 
		if(new LivroValidador.isValid(livro)){ 
			//executa o insert no banco 
		} 
	}  
      
}  

1º: Entendi corretamente, sergio?
2º: Neste caso o método isValid() está retornando um boolean. Porém ele também poderia retorna um objeto Validador para ser tratado por quem chamou a função, correto?

Você pode user um dos callbacks que o JPA tem: PrePersist, PreUpdate, PreDelete…

http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/listeners.html

No callback vc pode disparar uma exceção caso o object não esteja válido.

tveronezi,

Não estou utilizando JPA.
Sabe me dizer se a abordagem que mencionei está correta ou na classe DAO não deve conter validação?

[quote=CodeDeveloper]sergio,

Realmente você citou a composição, desculpe-me. Analisei o link passado e acho entendi o que você quis dizer.
Veja se a abordagem está correta, por favor:

De modo geral, a idéia é ter uma classe que valide Livro, centralizando as regras de validações e utilizando um validador.

public class LivroValidador{

	public boolean isValid(Livro livro){
		// Utilizar aqui o Validar e centralizar as regras de validação
	}



}

Então, utilizar essa classe em todos os pontos necessários para garantir a persistencia, como por exemplo em LivroDAO, CadastroController e etc.

public class CadastroController extends HttpServlet{  
      
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
      
		Livro livro = new Livro(req.getParameter("nome"),req.getParameter("valor"));
		// Utilização da classe validadora  
		if(new LivroValidador.isValid(livro)){  
			new LivroDAO.salvar(livro);  
 		}  
        }  
      
}
public class LivroDAO{  
      
	public void salvar(Livro livro){ 
		// Utilização da classe validadora 
		if(new LivroValidador.isValid(livro)){ 
			//executa o insert no banco 
		} 
	}  
      
}  

1º: Entendi corretamente, sergio?
[/quote]

Sim. O único ponto que faltou é que não é o mesmo validador que vc usa em todo o lugar porque as regras que vc está validando em cada camada são diferentes. O DAO por exemplo vai validar se o campo está preenchido, mas ele não sabe validar se o valor preenchido é correto conforme a regra de negocio. Isso é o que o service vai fazer. Em geral eu mantenho validadores de persistencia, que só uso nos daos e validadores de dominio que uso nos services. As regras dos validadores de dominio são realmente regras, as dos do DAO são salvaguardas (safety-check)

Não. Um validador que retorna um validador ? Ou seja, um objeto que se retorna a si mesmo ?

Um opção mais robusta é usar um método validate que não retorna um boolean. Retorna um objeto de resultado da validação (ValidationResult) e esse objeto que tem um isValid(). Lembre-se que quando o objeto não é válido, a razão de invalidação é mais importante que o boolean, normalmente vc vai querer ter acesso a essas razões. Essas razões ficam no ValidationResult numa simples lista ou coisa assim ( no link que eu passei do javabuilding tem a implementação de um ValidationResult relativamente sofisticado que vc pode usar como modelo). O ponto é, isValid é pouca informação. Codigo verdadeiros têm um else que mostra ao usuario o problema.

    public class CadastroController extends HttpServlet{    
            
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {    
            
            Livro livro = new Livro(req.getParameter("nome"),req.getParameter("valor"));  
            // Utilização da classe validadora    

             LivroValidator validator = new LivroValidator();

             ValidationResult result = validator.validate(livro);

            if(result.isValid()){    
                new LivroDAO.salvar(livro);    
            }    else {
                // mostra os erros para o usuario
                resp.setAtribute(result.getInvalidationReasons().get(0).getMessage()); // pega a mensagem da primeira razão de invalidação
            }
         }    
            
    }  

Só a camada de ui que pode diretamente mostrar as mensagens.
Na camada de serviços e de dao o else gera uma Exception ( normalmente uma ValidationException que contém o objeto ValidationResult).

O ponto é que são as mensagens que são importantes e não se é válido ou não. Ou seja, é mais importante saber porque é inválido, do que saber que é válido.

sergio,

Muito obrigado pela ajuda e pela ótima explicação detalhada!
Entendi realmente o que você exemplificou e agora vou aplicar isto no código e analisar o andamento.

Acredito que isso resolva o meu problema!
Qualquer coisa volto a postar neste tópico, ok??

Grande abraço!!

Você precisa decidir se quer criar algo no prazo ou um sistema à prova de tolos.

Se um programador não sabe o que está fazendo mas mesmo assim pode usar o sistema pra realizar alguma operação no banco de dados que o deixaria em estado inconsistente, então você tem problemas muito mais sérios do que como fazer validação em servlets.

Sinceramente acho que você está pensando muito sobre algo que deveria ser simples. Normal, acontece muito com quem está iniciando.

JoseIgnacio,

Correto. Mas você concorda que não necessariamente um programador não sabe o que faz, mas por falta de informação em um projeto extremamente corrido, ou por uma distração (algo que pode acontecer com qualquer pessoa exausta e sob pressão), este problema poderia ocorrer?

Você aconselha então não se preocupar com a validação na classe DAO e tentar ao máximo ter programadores totalmente alinhados com o projeto e ótimos técnicamente? Desta forma, supostamente ninguém faria diretamente um “new LivroDAO().salvar(new Livro());” ?

[quote=CodeDeveloper]JoseIgnacio,

Correto. Mas você concorda que não necessariamente um programador não sabe o que faz, mas por falta de informação em um projeto extremamente corrido, ou por uma distração (algo que pode acontecer com qualquer pessoa exausta e sob pressão), este problema poderia ocorrer?

Você aconselha então não se preocupar com a validação na classe DAO e tentar ao máximo ter programadores totalmente alinhados com o projeto e ótimos técnicamente? Desta forma, supostamente ninguém faria diretamente um “new LivroDAO().salvar(new Livro());” ?[/quote]

Exato. Se alguém pode fazer isso… compilar, testar, fazer o deploy, e ninguém percebeu nada, é porque existem problemas mais sérios que estão fora do escopo e alcance do seu software. Validação tb não vai impedir que alguém introduza incosistências conectando diretamente com o banco de dados. Então pra que se preocupar?

[quote=JoseIgnacio][quote=CodeDeveloper]JoseIgnacio,

Correto. Mas você concorda que não necessariamente um programador não sabe o que faz, mas por falta de informação em um projeto extremamente corrido, ou por uma distração (algo que pode acontecer com qualquer pessoa exausta e sob pressão), este problema poderia ocorrer?

Você aconselha então não se preocupar com a validação na classe DAO e tentar ao máximo ter programadores totalmente alinhados com o projeto e ótimos técnicamente? Desta forma, supostamente ninguém faria diretamente um “new LivroDAO().salvar(new Livro());” ?[/quote]

Exato. Se alguém pode fazer isso… compilar, testar, fazer o deploy, e ninguém percebeu nada, é porque existem problemas mais sérios que estão fora do escopo e alcance do seu software. Validação tb não vai impedir que alguém introduza incosistências conectando diretamente com o banco de dados. Então pra que se preocupar?
[/quote]

É um ponto de vista interessante. Na verdade como você mesmo disse, os iniciantes devem ter essa preocupação quando estão começando… eu tenho a impressão que tenho que fazer de tudo ao meu alcançe para garantir a consistencia dos dados. Porém concordo com você também. Até que ponto isso vale a pena?

Obrigado por compartilhar Jose!!