Fluxo de pedido
Uma máquina de estados explícita, que recusa a transição impossível e guarda o histórico.
Um pedido tem estados, e os bugs caros moram nas transições: o pedido cancelado que é despachado, o pago que volta a aguardar pagamento. Com o estado num texto livre, nada impede isso. Com uma tabela de transições, a transição impossível é recusada — e o histórico diz quem mudou o quê.
| Peça | O que ela exercita |
|---|---|
| enum | os estados possíveis — e só eles |
| tabela de transições | de onde se pode ir para onde |
| histórico | cada mudança com quem e quando |
match exaustivo | o check avisa do estado esquecido |
Estrutura#
text
pedidos/
src/
estados.df o enum e a tabela
pedido.df transicionar, historico
tests/
fluxo_test.df um trial por transicao PROIBIDAforge.toml
[project]
name = "pedidos"
version = "0.1.0"
description = "Fluxo de pedidos"
entry = "src/main.df"
dataforge = ">=1.1"
[dependencies]
[scripts]
start = "run src/main.df"
test = "test tests/"
O núcleo#
Este bloco roda sozinho — copie para um arquivo e rode dataforge run. Ele termina com assert, e é assim que esta página é conferida a cada build.
src/pedido.df
enum Estado:
Criado
Pago
Separado
Enviado
Entregue
Cancelado
TRANSICOES := {
"Criado": ["Pago", "Cancelado"],
"Pago": ["Separado", "Cancelado"],
"Separado": ["Enviado"],
"Enviado": ["Entregue"],
"Entregue": [],
"Cancelado": []
}
blueprint Pedido(id):
estado := Estado.Criado
historico: Cluster := []
action mover(para, quem):
permitidos := TRANSICOES[self.estado.name]
given para.name not in permitidos:
trigger $"pedido {self.id}: '{self.estado.name}' nao vai para '{para.name}'. Pode ir para: {permitidos}"
self.historico.append({"de": self.estado.name, "para": para.name, "quem": quem})
self.estado := para
action terminal():
yield len(TRANSICOES[self.estado.name]) is 0
p := spawn Pedido(42)
p.mover(Estado.Pago, "gateway")
p.mover(Estado.Separado, "ana")
p.mover(Estado.Enviado, "transportadora")
monitor:
p.mover(Estado.Cancelado, "cliente")
assert no
handle Error as e:
out e.message
p.mover(Estado.Entregue, "transportadora")
assert p.terminal()
assert len(p.historico) is 4
assert p.historico[0]["quem"] is "gateway"O teste#
No projeto, a regra mora em src/ e o teste a importa pelo caminho relativo — dataforge test tests/ descobre o arquivo sozinho.
tests/nucleo_test.df
adopt ../src/pedido as P
crucible "transicoes proibidas":
trial "cancelado nao e enviado":
p := spawn P.Pedido(1)
p.mover(P.Estado.Cancelado, "x")
expect(lambda => p.mover(P.Estado.Enviado, "x")).to_raise()As decisões#
| Decisão | Sem ela |
|---|---|
o estado é um enum | "Pagu" é aceito e o pedido some de todo relatório |
| a tabela fica num lugar só | a regra “cancelado não volta” está escrita em cinco rotas, e uma esquece |
| a mensagem lista para onde pode ir | quem integra descobre as transições por tentativa e erro |
| o histórico nasce com a transição | o cliente pergunta quem cancelou, e ninguém sabe |
Para ir além#
- A máquina genérica da biblioteca:
Padroes.maquina— Arcane.Padroes. - Transição que dispara evento de domínio: Arcane.Dominio.
- Receita: Máquina de estados.
Volte para todos os tipos de projeto.