Validar mais de uma exception para validações de regra de negócio

7 respostas Resolvido
javaspringexception
D

Bom dia,

Estou com uma dúvida/problema referente as validações por exceptions.

Possuo no projeto controller/service/repository.

No service eu faço as regras de negócio da app, e através do controller chamo um método por exemplo salvar que salvo a entidade.

Anteriormente eu estava mandando no método salvar a entidade e um BindResult para dentro do service, porém achei isso uma má prática. Resolvi então tratar com exceptions e não mais validar na mão e para cada falha eu adicionar no (bindResult.rejectValue).

O problema: como em cada regra que criei uma exception, quando eu aciono o salvar, a primeira exception que ele entra o sistema retorna para tratar.

Ex: tenho regra para validar se o CPF é válido e o Email ja estão cadastrados.

Quando a primeira regra cai na exception ele não valida as demais, desta forma apresenta a mensagem de CPF inválido e não a do email. Precisa assim o usuário corrigir a situação e submeter novamente, aí sim ele gera excessão de segunda.

Existe uma forma de fazer as exception serem lançadas juntas, ou, existe uma outra forma para um caso como este?

Obs: Estou utilizando Spring Boot mvc

Agradeço desde já.

Diego Ricardo Cossa.

7 Respostas

L

Você pode ter apenas uma Exception genérica, digamos assim, e ir validando sequencialmente, inserindo cada mensagem em uma lista, por exemplo. Se a lista estiver vazia, retorna que a validação foi feita com sucesso, senão, lança a exception e empilha as mensagens presentes na lista para exibição.

D

Certo, no momento que eu disparo o throw new ExceptionGenerica para a validacao de CPF por exemplo ele já vai parar…

Como empilhar as exceptions sem que pare e retorne para o catch?

L

Isso vai acontecer por quê a palavra reservada throw indica a JVM que esta instrução deve ser encerrada imediatamente e retornar ao método que a chamou.

Algo assim

List<String> mensagens = new ArrayList<>();

if(!/*condição de validação da abóbora*/){
    lista.add("Abóbora inválida");
}

if(!/*condição de validação para cenoura*/){
    lista.add("Cenoura inválida");
}

if(!lista.isEmpty()){
    StringBuffer msg = new StringBuffer();
    for(String m : mensagens){
        msg.append(m);
        msg.append("\n");
    }
    throw new Exception(msg.toString());
}
L

Diego, você pode fazer da forma que o @Luis_Augusto_Santos mostrou ou seguinte forma:

Ter uma classe de Exception

public class ValidationException extends Exception {

	private static final long serialVersionUID = 1L;

	private String message;
	private List<String> errors;

	public ValidationException(String message, List<String> errors) {
		this.message = message;
		this.errors = errors;
	}

	public List<String> getErrors() {
		return errors;
	}
	
}

E no seu service validar da seguinte forma:

List<String> errors = new ArrayList<>();
		
		if(!cpf.isValid()){
			errors.add("CPF Inválido!");
		}
		
		if(!email.isValid()){
			errors.add("E-mail Inválido!")
		}
		
		if(!errors.isEmpty()) {
			throw new ValidationException("Problemas na validação.", errors);
		}

Ai no caso você pode ter um Try/Catch no seu Controller, recuperar a lista de erros ( e.getErrors() ) e retornar na tela da melhor forma que achar.

S
Solucao aceita

Escrevi uma API de regras reutilizáveis, que resolve esse tipo de problema.
Me baseei num padrão chamado Specification.

Com essa API você consegue validar todas suas regras de negócio e só ao final tratar as exceções necessárias.

Vejamos o exemplo hipotético de uma classe Pessoa, a qual desejamos validar o nome, idade e sexo:

class Pessoa {
	 
    String nome;
    int idade;
    char sexo;

    Pessoa(String nome, int idade, char sexo) {
        this.nome = nome;
        this.idade = idade;
        this.sexo = sexo;
    }
}

Aí implementamos a regra para validar o nome:

import br.com.staroski.rules.*;

// Especificação da regra que valida o nome de uma Pessoa
class Nome implements Specification<Pessoa> {

	@Override
	public void verify(Pessoa pessoa) throws UnattendedException {
		if (!pessoa.nome.matches("[A-Z]{1}[a-z]+")) {
			throw new UnattendedException("Nome precisa começar com letra maiuscula e ter pelo menos duas letras");
		}
	}
}

A regra para validar a idade:

import br.com.staroski.rules.*;

// Especificação da regra que valida a idade de Pessoa
class Idade implements Specification<Pessoa> {

	@Override
	public void verify(Pessoa pessoa) throws UnattendedException {
		if (pessoa.idade < 0) {
			throw new UnattendedException("Idade não pode ser negativa");
		}
	}
}

E a regra para validar o sexo:

import br.com.staroski.rules.*;

// Especificação da regra que valida o sexo de uma Pessoa
class Sexo implements Specification<Pessoa> {

	@Override
	public void verify(Pessoa pessoa) throws UnattendedException {
		switch (pessoa.sexo) {
			case 'M':
			case 'F':
				return;
			default:
				throw new UnattendedException("Sexo só pode ser 'M' ou 'F'");
		}
	}
}

Agora que as regras foram implementadas, segue um exemplo de como utilizá-las para validar as informações de um objeto do tipo Pessoa:

import br.com.staroski.rules.*;

public class Exemplo {

	public static void main(String[] args) {
		// instanciamos as regras a partir das especificações 
		Rule<Pessoa> nome = Rule.create(new Nome());
		Rule<Pessoa> idade = Rule.create(new Idade());
		Rule<Pessoa> sexo = Rule.create(new Sexo());

		// criamos uma pessoa com nome, idade e sexo validos
		Pessoa pessoa = new Pessoa("Fulano", 30, 'M');
		// criamos uma regra só que corresponde às três regras: nome, idade e sexo
		// e validamos com um único if
		if (nome.and(idade).and(sexo).isSatisfiedBy(pessoa)) {
			System.out.println("Teste 1");
			System.out.println("O nome, idade e sexo da pessoa atendem as regras\n");
		}



		// criamos uma pessoa com nome, idade e sexo inválidos
		pessoa = new Pessoa("FuLaNo", -1, 'S');
		// criamos uma regra só que corresponde às três regras: nome, idade e sexo
		// armazenamos essa regra numa variável
		Rule<Pessoa> regra = nome.and(idade).and(sexo);
		// assim, validamos as três regras, com um único if
		if (regra.not().isSatisfiedBy(pessoa)) {
			System.out.println("Teste 2");
			System.out.println("A pessoa não atendeu as seguintes regras:");
			// se a pessoa não atendeu às regras,
			// usamos a variável declarada para obter os detalhes
			for (String detalhe : regra.getDetails()) {
				System.out.println(detalhe);
			}
		}
	}
}
D

Agradeço a resposta de todos, ambos os casos podem atender, vou analisar o custo de desenvolvimento de cada uma delas e ver o que vai atender melhor a necessidade.

Obrigado a todos.

L

A solução do @staroski é mais elegante e mais funcional. Você precisará de tempo para aprender a usar essa solução, mas verá que ela é muito mais adequada ao que deseja.

Criado 7 de abril de 2017
Ultima resposta 7 de abr. de 2017
Respostas 7
Participantes 4