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! 😉

Author Of article : Uiratan Cavalcante Read full article