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#
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.
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 negativo3. 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.
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 anotadoA 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.