Statelyai e XState: modelagem de estados complexos com máquinas de estado

1. Fundamentos da Modelagem de Estados com Máquinas de Estado

1.1. Conceitos clássicos: estados, transições, eventos e ações

Máquinas de estado finito (FSM) são modelos computacionais compostos por um conjunto finito de estados, transições entre eles, eventos que disparam essas transições e ações executadas durante o processo. Um sistema pode estar em apenas um estado por vez, e cada transição é determinística: dado um estado atual e um evento, a máquina sempre irá para o mesmo próximo estado.

Estado A --[evento X]--> Estado B
Estado B --[evento Y]--> Estado C

1.2. Por que máquinas de estado finito (FSM) são insuficientes para sistemas modernos

FSMs clássicas sofrem de "explosão de estados" em sistemas complexos. Considere um formulário de cadastro com validação de email, verificação de CPF, upload de documento e confirmação por SMS. Representar todas as combinações possíveis como estados planos resulta dezenas de estados — muitos deles redundantes. Além disso, FSMs não suportam concorrência real: se dois processos precisam rodar simultaneamente (ex: validação de dados enquanto o usuário digita), a modelagem torna-se artificial e frágil.

1.3. Introdução ao paradigma de statecharts: hierarquia, paralelismo e guardas

David Harel propôs os statecharts em 1987 como extensão das FSMs. Três conceitos fundamentais resolvem as limitações:

  • Hierarquia: estados podem conter subestados (aninhamento)
  • Paralelismo: regiões ortogonais executam simultaneamente
  • Guardas: condições booleanas que controlam transições

Esses elementos permitem modelar sistemas reais com clareza e sem redundância.

2. XState: A Biblioteca que Traduz Statecharts para Código

2.1. Instalação e configuração básica do XState

npm install xstate @xstate/react

2.2. Sintaxe de definição de máquinas: estados, eventos e transições simples

import { createMachine } from 'xstate';

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inativo',
  states: {
    inativo: {
      on: { TOGGLE: 'ativo' }
    },
    ativo: {
      on: { TOGGLE: 'inativo' }
    }
  }
});

2.3. Tipos de nós: estados atômicos, compostos, paralelos e finais

  • Atômico: estado simples sem filhos
  • Composto: contém subestados (hierarquia)
  • Paralelo: múltiplas regiões simultâneas
  • Final: estado terminal que encerra a máquina
const compoundMachine = createMachine({
  initial: 'idle',
  states: {
    idle: { on: { START: 'working' } },
    working: {
      type: 'compound',
      initial: 'processing',
      states: {
        processing: { on: { DONE: 'completed' } },
        completed: { type: 'final' }
      }
    }
  }
});

3. Modelagem de Estados Complexos com Hierarquia e Paralelismo

3.1. Estados aninhados: representando subfluxos de negócio

Um assistente de compras pode ter o estado checkout com subestados carrinho, pagamento e confirmacao. Cada subestado gerencia suas próprias transições internas.

const checkoutMachine = createMachine({
  initial: 'carrinho',
  states: {
    carrinho: { on: { PROSSEGUIR: 'pagamento' } },
    pagamento: {
      initial: 'aguardando',
      states: {
        aguardando: { on: { PAGAR: 'processando' } },
        processando: { on: { SUCESSO: 'pago', FALHA: 'falhou' } },
        pago: { type: 'final' },
        falhou: { on: { TENTAR_NOVAMENTE: 'aguardando' } }
      }
    },
    confirmacao: { type: 'final' }
  }
});

3.2. Regiões paralelas: executando múltiplos estados simultaneamente

Um dashboard de monitoramento pode exibir status de servidor e conexão de banco em paralelo.

const dashboardMachine = createMachine({
  type: 'parallel',
  states: {
    servidor: {
      initial: 'online',
      states: {
        online: { on: { FALHA: 'offline' } },
        offline: { on: { RECUPERAR: 'online' } }
      }
    },
    banco: {
      initial: 'conectado',
      states: {
        conectado: { on: { PERDA: 'desconectado' } },
        desconectado: { on: { RECONECTAR: 'conectado' } }
      }
    }
  }
});

3.3. Estados finais e estados de histórico: retomando fluxos interrompidos

Estados de histórico (history) permitem retornar ao último subestado ativo antes de uma transição. Útil em formulários que o usuário pode salvar e retomar.

const formMachine = createMachine({
  initial: 'edicao',
  states: {
    edicao: {
      initial: 'dados_pessoais',
      states: {
        dados_pessoais: { on: { AVANCAR: 'endereco' } },
        endereco: { on: { AVANCAR: 'revisao' } },
        revisao: { on: { ENVIAR: '#final' } }
      },
      on: {
        PAUSAR: 'pausado'
      }
    },
    pausado: {
      type: 'compound',
      on: {
        RETOMAR: 'edicao.history'
      }
    }
  }
});

4. Ações, Guardas e Serviços: Comportamento Dinâmico

4.1. Ações de entrada, saída e transição: efeitos colaterais controlados

const machine = createMachine({
  initial: 'aguardando',
  states: {
    aguardando: {
      entry: 'logEntrada',
      exit: 'logSaida',
      on: {
        INICIAR: {
          target: 'processando',
          actions: 'logTransicao'
        }
      }
    },
    processando: { /* ... */ }
  }
}, {
  actions: {
    logEntrada: () => console.log('Entrou em aguardando'),
    logSaida: () => console.log('Saiu de aguardando'),
    logTransicao: () => console.log('Transição INICIAR')
  }
});

4.2. Guardas condicionais: decidindo rotas com base em contexto

const authMachine = createMachine({
  context: { usuario: null, permissoes: [] },
  initial: 'verificando',
  states: {
    verificando: {
      on: {
        LOGIN: [
          { target: 'admin', cond: 'ehAdmin' },
          { target: 'usuario', cond: 'ehUsuario' },
          { target: 'negado' }
        ]
      }
    },
    admin: { /* ... */ },
    usuario: { /* ... */ },
    negado: { /* ... */ }
  }
}, {
  guards: {
    ehAdmin: (ctx) => ctx.permissoes.includes('admin'),
    ehUsuario: (ctx) => ctx.usuario !== null
  }
});

4.3. Invocação de serviços: chamadas assíncronas, promises e máquinas filhas

const fetchMachine = createMachine({
  initial: 'idle',
  states: {
    idle: { on: { FETCHAR: 'carregando' } },
    carregando: {
      invoke: {
        src: 'buscarDados',
        onDone: { target: 'sucesso', actions: 'salvarDados' },
        onError: { target: 'erro', actions: 'logErro' }
      }
    },
    sucesso: { /* ... */ },
    erro: { on: { TENTAR_NOVAMENTE: 'carregando' } }
  }
}, {
  services: {
    buscarDados: () => fetch('/api/dados').then(r => r.json())
  }
});

5. Statelyai: Ferramentas Visuais para Design e Depuração

5.1. Stately Studio: editor gráfico de statecharts

O Stately Studio (anteriormente XState Viz) é um ambiente visual onde você arrasta e solta estados, conecta transições, define guardas e ações — tudo com feedback visual imediato.

5.2. Visualização e simulação de máquinas em tempo real

Durante a simulação, você pode disparar eventos manualmente, observar mudanças de estado, inspecionar o contexto e visualizar o caminho percorrido. Ideal para depuração e documentação.

5.3. Exportação de código e integração com XState

Após modelar visualmente, o Studio exporta código pronto para uso com XState, mantendo toda a lógica de estados, guardas e ações. A integração é bidirecional: você pode importar máquinas existentes para edição visual.

6. Padrões Avançados e Casos de Uso Reais

6.1. Máquinas de estado para formulários multi-etapas

Formulários com validação em cada etapa, salvamento automático e retomada posterior são modelados naturalmente com estados aninhados e histórico.

6.2. Gerenciamento de fluxos de autenticação e autorização

Fluxos de login, recuperação de senha, autenticação de dois fatores e refresh de token encaixam-se perfeitamente em máquinas com regiões paralelas e guardas condicionais.

6.3. Orquestração de workflows com máquinas aninhadas e eventos globais

Sistemas de aprovação de documentos, pipelines de CI/CD e processos de onboarding podem ser modelados como máquinas pai que orquestram máquinas filhas, comunicando-se via eventos.

7. Integração com Frameworks e Ecossistema

7.1. Uso com React: hooks useMachine e useService

import { useMachine } from '@xstate/react';
import { toggleMachine } from './toggleMachine';

function Toggle() {
  const [state, send] = useMachine(toggleMachine);
  return (
    <button onClick={() => send('TOGGLE')}>
      {state.matches('ativo') ? 'Ligado' : 'Desligado'}
    </button>
  );
}

7.2. Integração com Vue, Angular e Svelte

XState oferece pacotes oficiais para Vue (@xstate/vue), Angular (@xstate/angular) e adaptadores para Svelte. A lógica permanece independente do framework.

7.3. Testabilidade: escrevendo testes unitários para máquinas de estado

import { interpret } from 'xstate';
import { toggleMachine } from './toggleMachine';

test('deve alternar entre ativo e inativo', () => {
  const service = interpret(toggleMachine).start();
  expect(service.state.matches('inativo')).toBe(true);
  service.send('TOGGLE');
  expect(service.state.matches('ativo')).toBe(true);
  service.send('TOGGLE');
  expect(service.state.matches('inativo')).toBe(true);
});

8. Limitações, Armadilhas e Boas Práticas

8.1. Quando evitar máquinas de estado: cenários de baixa complexidade

Para componentes com 2-3 estados simples (ex: botão de like), uma máquina de estado é excesso de engenharia. Use useState ou estado local.

8.2. Gerenciamento de estado global vs. máquinas locais

Máquinas de estado não substituem gerenciadores de estado global (Redux, Zustand). Use máquinas para lógica de fluxo e estado global para dados compartilhados entre componentes não relacionados.

8.3. Boas práticas: nomes de eventos, versionamento e documentação visual

  • Use eventos no passado (DADOS_CARREGADOS, FORMULARIO_ENVIADO)
  • Versionamento semântico para máquinas exportadas
  • Mantenha diagramas atualizados no Stately Studio como documentação viva

Referências