Protegendo Invariantes no Design de Software
No design de software, especialmente em Domain-Driven Design (DDD) e boas práticas de programação, proteger as invariantes é essencial para garantir que o sistema funcione de maneira previsível e confiável. Mas o que isso significa na prática? Vamos explorar esse conceito e como aplicá-lo corretamente.
🔍 O que são Invariantes?
Invariantes são regras ou condições que sempre devem ser verdadeiras dentro de um determinado contexto do sistema. Elas garantem a coerência e integridade dos dados e previnem estados inválidos. Alguns exemplos comuns incluem:
- Um saldo bancário não pode ser negativo.
- Um pedido precisa ter pelo menos um item.
- Um e-mail de usuário deve ser válido.
Se essas condições forem violadas, o sistema pode entrar em um estado inconsistente, causando falhas ou resultados inesperados.
🔒 Como Proteger as Invariantes?
A melhor forma de proteger as invariantes é garantir que nenhuma operação do sistema possa violá-las. Isso pode ser feito por meio de técnicas como:
1️⃣ Encapsulamento e Controle de Estado
Não expor diretamente os atributos mutáveis de uma entidade. Em vez disso, forneça métodos controlados para modificar o estado.
❌ Exemplo Ruim:
public class ContaBancaria {
public BigDecimal saldo;
}
Este código permite que qualquer parte do sistema altere o saldo diretamente, o que pode levar a valores inválidos.
✅ Exemplo Correto:
public class ContaBancaria {
private BigDecimal saldo;
public ContaBancaria(BigDecimal saldoInicial) {
if (saldoInicial.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Saldo inicial não pode ser negativo.");
}
this.saldo = saldoInicial;
}
public void sacar(BigDecimal valor) {
if (valor.compareTo(saldo) > 0) {
throw new IllegalArgumentException("Saldo insuficiente.");
}
saldo = saldo.subtract(valor);
}
}
Aqui, garantimos que nenhum código externo possa modificar o saldo de forma inválida.
2️⃣ Agregados e Regras de Consistência no DDD
No DDD, um Aggregate Root é responsável por proteger as invariantes do agregado. Isso impede que partes externas alterem os objetos internos de maneira inconsistente.
public class Pedido {
private List<ItemPedido> itens = new ArrayList<>();
public void adicionarItem(ItemPedido item) {
if (item.getQuantidade() <= 0) {
throw new IllegalArgumentException("Quantidade deve ser positiva.");
}
itens.add(item);
}
}
Aqui, não permitimos que um item com quantidade inválida seja adicionado ao pedido.
3️⃣ Notification Pattern para Validação
Em algumas situações, lançar exceções pode não ser a melhor abordagem. Podemos usar o Notification Pattern, que acumula notificações de erro sem interromper a execução imediatamente.
public class Cliente {
private String email;
private List<String> notificacoes = new ArrayList<>();
public Cliente(String email) {
if (!email.contains("@")) {
notificacoes.add("E-mail inválido.");
} else {
this.email = email;
}
}
public boolean isValido() {
return notificacoes.isEmpty();
}
}
Aqui, a validação não impede a criação do objeto, mas permite que o sistema verifique se há problemas antes de prosseguir.
4️⃣ Objetos de Valor (Value Objects)
Objetos imutáveis ajudam a proteger invariantes porque não podem ser modificados após a criação.
public class CPF {
private final String numero;
public CPF(String numero) {
if (!numero.matches("\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2}")) {
throw new IllegalArgumentException("CPF inválido.");
}
this.numero = numero;
}
public String getNumero() {
return numero;
}
}
Isso garante que um CPF sempre será válido, pois não pode ser alterado após sua criação.
⚠️ Consequências de Não Proteger as Invariantes
Se as invariantes não forem protegidas, o sistema pode apresentar comportamentos imprevisíveis. Alguns problemas comuns incluem:
- Dados inconsistentes, como pedidos com preço negativo.
- Bugs difíceis de rastrear devido a efeitos colaterais inesperados.
- Falhas em cálculos financeiros devido a valores inválidos sendo processados.
🏁 Conclusão
Proteger as invariantes é fundamental para garantir a consistência e confiabilidade do sistema. Técnicas como encapsulamento, agregados no DDD, Notification Pattern e Value Objects ajudam a garantir que os objetos sempre permaneçam em um estado válido.
Aplicar essas práticas melhora a qualidade do código e reduz o risco de erros críticos. Comece a adotá-las no seu projeto e veja a diferença! 🚀
💬 O que você acha dessa abordagem? Já enfrentou problemas por não proteger invariantes? Compartilhe sua experiência nos comentários! 😉
Top comments (0)