DEV Community

Uiratan Cavalcante
Uiratan Cavalcante

Posted on

Protegendo Invariantes no Design de Software

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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)