Pular para o conteúdo

Agregados e invariantes

A única porta de escrita, a invariante cobrada na saída, e o comando que desfaz.

Um agregado é a única porta de escrita de um grupo de objetos. Três cobranças sustentam isso, e cada uma evita um defeito diferente.

1. O estado só muda por comando#

dataforge
pedido := D.agregado("Pedido", "PED-7", total := 0, itens := 0)
pedido.invariante("o total nunca e negativo",
    lambda p => p.ler("total", 0) bigger_eq 0)
pedido.invariante("um pedido tem no maximo 3 itens",
    lambda p => p.ler("itens", 0) smaller_eq 3)

pedido.mudar(total := -1)
// erro: 'Pedido' so muda dentro de um comando.

Se qualquer um escreve, a invariante não vale nada — porque não há onde cobrá-la. O comando é o lugar.

2. A invariante é cobrada na SAÍDA#

Cobrar na entrada deixa o objeto quebrado quando o comando falha no meio. Cobrar na saída garante que ninguém observa um estado inválido — e é isso que faz o agregado ser a única porta.

dataforge
mark @pedido.comando("descontar")
action descontar(p, quanto):
    p.mudar(total := p.ler("total", 0) - quanto)

monitor:
    pedido.descontar(999)
handle Error as e:
    out e.message      // 'Pedido' violou: o total nunca e negativo

3. O comando que falha no meio é desfeito por inteiro#

Estado e eventos voltam ao que eram, e a versão não avança. Sem isso, metade da mudança fica aplicada e a próxima leitura vê um agregado que nunca deveria existir — inclusive um evento de um comando que não aconteceu, que é a pior classe de fato.

dataforge
assert pedido.ler("total") is 50      // como antes da tentativa
assert pedido.versao() is 2           // a versao tambem voltou
assert len(pedido.eventos()) is 2     // nada novo foi anotado

A invariante que estoura é um bug dela#

Como na regra de um valor: um erro dentro da condição vira uma mensagem dizendo que a invariante quebrou, e não que o estado é inválido. As duas coisas exigem correções em lugares diferentes.