Código limpo e Extensível
- #PHP
- #Java
Bom, temos uma API de cadastramento de pessoas para uma locadora de carros. Para uma pessoa realizar seu cadastro, ela precisa informar sua data de nascimento, seu nome, seu CPF, o número de cartão de crédito e um endereço.
Temos a classe ClientService, ela tem um método saveClient, responsável por validar alguns dados como, o CPF e o endereço, e persiste o cliente na base de dados.
@Service
public class ClientService {
@Autowired
private ClientRepository repository;
@Autowired
private AddressInfra addressInfra;
public Client saveClient(Client client) {
if (repository.existsByCpf(client.getCpf())) {
throw new CpfExistException("Cpf already exist");
}
if (addressInfra.validationAdress(client.getAddress()) == null) {
throw new InvalidAddressException("CEP invalid");
}
return repository.save(client);
}
}
Observe que o método utiliza if’s para validar cada dado. Acho que você notou que a aplicação não exige o número da CNH do cliente, agora além da adição desse atributo na classe Client, também é necessário validar esse dado, então vamos adicionar mais um if no método saveClient. Adicionar novos if's ao método vai tornar a leitura humana e a manutenção difícil.
@Service
public class ClientService {
@Autowired
private ClientRepository repository;
@Autowired
private AddressInfra addressInfra;
@Autowired
private CnhInfra cnhInfra;
public Client saveClient(Client client) {
if (repository.existsByCpf(client.getCpf())) {
throw new CpfExistException("Cpf already exist");
}
if (addressInfra.validationAdress(client.getAddress()) == null) {
throw new InvalidAddressException("CEP invalid");
}
if (!cnhInfra.validationCNH(client.getCNH)){
throw new InvalidCNHException("CNH invalid");
}
return repository.save(client);
}
}
Para resolver esse problema, devemos aplicar o princípio da Responsabilidade Única(“Uma classe deve ter um, e somente um, motivo para mudar”), criando um método separado para lidar com as validações, chamado validations.
@Service
public class ClientService {
@Autowired
private ClientRepository repository;
@Autowired
private AddressInfra addressInfra;
@Autowired
private CnhInfra cnhInfra;
public Client saveClient(Client client) {
validations(client);
return repository.save(client);
}
private void validations(Client client) {
if (repository.existsByCpf(client.getCpf())) {
throw new CpfExistException("Cpf already exist");
}
if (addressInfra.validationAdress(client.getAddress()) == null) {
throw new InvalidAddressException("CEP invalid");
}
if (!cnhInfra.validationCNH(client.geetCNH)) {
throw new InvalidCNHException("CNH invalid");
}
}
}
Porém, cada nova validação adicionada ainda resultará em um novo if no método validations. Para solucionar esse problema, devemos aplicar o princípio do Aberto e Fechado(“Objetos ou entidades devem estar abertos para extensão, mas fechados para modificação”), criando classes para cada validação que implementem a interface Validation e utilizem o método validator.
@Component
public interface Validation {
void validator(Client client);
}
@Component
public class AddressValidation implements Validation {
@Autowired
AddressInfra addressInfra;
@Override
public void validator(Client client) {
Optional.ofNullable((addressInfra.validationAdress(client.getAddress())))
.orElseThrow(() -> new InvalidAddressException("CEP invalid"));
}
}
@Component
public class CpfValidation implements Validation {
@Autowired
ClientRepository clientRepository;
@Override
public void validator(Client client) {
if (clientRepository.existsByCpf(client.getCpf())){
throw new CpfExistException("Cpf already exist");
}
}
}
Com a ajuda do Framework Spring, podemos injetar uma lista de Validation na classe ClientService , e utilizar o método forEach para executar a validação de cada classe que está implementando a interface Validation.
@Service
public class ClientService {
@Autowired
private ClientRepository repository;
@Autowired
List<Validation> validations;
public ClientDto saveClient(Client client){
validations.forEach(validation -> validation.validator(client));
return repository.save(client);
}
}
Se você não notou, acabamos de usar o Princípio da inversão da dependência, outro princípio do SOLID. Ou seja, a classe ClientService não depende e não tem relações com as classes CpfValidation e AddressValidation, a única relação da classe ClientService é com a interface Validation que é uma abstração.
Agora temos um código extensível, limpo e com as responsabilidades bem distribuídas. Vale a pena lembrar que não existe “Bala de prata” e sempre é bom avaliar caso-a-caso!