Solucionado (ver solução)
Solucionado
(ver solução)
3
respostas

Dúvida em relação as validações e a classe service

Estou desenvolvendo outro projeto e me surgiu uma dúvida em relação as validações e a estrutura de projeto e boas práticas.

Eu criei uma classe service e deixei ela responsável por fazer as validações


@Service
public class TargetService {

    @Autowired
    private TargetRepository targetRepository;

    public void validateTargetAndCurrentAmountToRegister(TargetDTO data) {
        if (data.targetAmount() <= data.currentAmount()) {
            throw new ValidateException("The current amount cannot be equal to or greater than the target current");
        }
    }

    public void validateTargetAndCurrentAmountToUpdate(TargetUpdateDTO data) {
        var targetBeforeUpdate = targetRepository.getReferenceById(data.id());
        var targetAmount = data.targetAmount();
        var currentAmount = data.currentAmount();

        if (targetAmount != null && currentAmount != null && currentAmount > targetAmount) {
            throw new ValidateException("The current amount cannot be greater than the target current");
        }
        else if (targetAmount != null && currentAmount == null) {
            currentAmount = targetBeforeUpdate.getCurrentAmount();
            if (currentAmount > targetAmount) {
                throw new ValidateException("The current amount cannot be greater than the target current");
            }
        }
        else if (targetAmount == null && currentAmount != null) {
            targetAmount = targetBeforeUpdate.getTargetAmount();
            if (currentAmount > targetAmount) {
                throw new ValidateException("The current amount cannot be greater than the target current");
            }
        }

    }

}

E nos meus controllers eu chamei essas validações

@PutMapping
    @Transactional
    public ResponseEntity update(@RequestBody TargetUpdateDTO data) {
        targetService.validateTargetAndCurrentAmountToUpdate(data);
        var target = targetRepository.getReferenceById(data.id());
        target.update(data);

        return ResponseEntity.ok(new TargetResponseDTO(target));
    }

Porém, pelo que eu entendi, não é uma boa deixar validações de regra de negócio no controller. Qual seria a melhor maneira de arrumar isso, jogar toda a lógica do controller para a classe service e criar outra classe somente para as validações e o service chama-las, deixar assim ou teria outra maneira mais apropriada?

3 respostas

Você pode abstrair a lógica inteira pra dentro do seu service, é super normal. Aqui vai um exemplo de um endpoint de update que já executa toda a lógica e responde em uma única linha no controller:

    @PutMapping("/publications/update")
    @Transactional
    protected ResponseEntity<PublicationResponseDTO> updatePublication(@RequestBody @Valid PublicationUpdateDTO dto) {
        return ResponseEntity.ok(publicationService.updatePublication(dto));
    }

Esse endpoint executa um método do service que realiza toda a lógica por trás do update e já retorna o DTO de resposta. Essa abordagem respeita o princípio da separação de responsabilidades, onde o controller só precisa se preocupar em receber e responder, o service só precisa se preocupar em realizar a lógica de negócio, assim como o repository só tem a responsabilidade de recuperar dados do banco de dados.

E mudando um pouquinho de assunto, aquele seu método validateTargetAndCurrentAmountToRegister você pode analisar se faz sentido remove-lo do service e coloca-lo dentro do próprio TargetDTO e anota-lo com @AssertTrue ou @AssertFalse, parecido com isso aqui:

    @AssertFalse(message = "The current amount cannot be equal to or greater than the target current")
    public void validateTargetAndCurrentAmountToRegister(TargetDTO data) {
        return data.targetAmount() <= data.currentAmount();
    }

Se a resposta não for false a validação vai falhar e o Jakarta Validation vai lançar uma exception com a mensagem definida na anotação. Assim o seu service não ficar responsavel demais por validações que às vezes só são usadas em uma única parte do código.

Entendi Mateus, porém a parte do @AssertFalse eu não consegui usar, não cai no erro nenhuma vez, continua permitindo eu cadastrar o currentAmount sendo maior que o targetAmount.

E a parte do service eu fiz, ficou bem melhor mesmo, Valeu.

solução!

Entendi, eu contruí o método do DTO em cima do método do service, acabei esquecendo de ajustar a estrutura do método, por isso a validação não tá funcionando

// em vez disso
    @AssertFalse(message = "The current amount cannot be equal to or greater than the target current")
    public void validateTargetAndCurrentAmountToRegister(TargetDTO data) {
        return data.targetAmount() <= data.currentAmount();
    }
    
// faça assim no DTO
    @AssertFalse(message = "The current amount cannot be equal to or greater than the target current")
    public void validateTargetAndCurrentAmountToRegister() {
        return this.targetAmount() <= this.currentAmount();
    }


// e no controller fica assim
public ResponseEntity<?> demonstracao(@Valid TargetDTO dto) {
    ...
}

Daí é só usar o @Valid que assim como os atributos anotados, esse método também será validado assim que o request chegar