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

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.

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.

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?

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());
}
1 curtida

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.

1 curtida

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);
			}
		}
	}
}

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.

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.

1 curtida