Quando a interface na cloud precisa de imprimir uma etiqueta física numa impressora USB instalada num armazém, é necessária uma solução arquitetónica criativa. Eis o padrão que construí.
O Problema
O frontend do meu ERP personalizado corre na Retool Cloud. A minha impressora de etiquetas (Brother QL-700) está ligada por USB a um PC Windows no armazém. A minha base de dados (NeonDB) está na cloud. Quando um colaborador clica em "Imprimir Etiqueta" na Retool, tem de sair uma etiqueta física de uma impressora situada a 3000 km dos servidores da Retool.
A Retool Cloud não consegue comunicar com uma impressora local. Não há um caminho de rede direto. O port forwarding é frágil. As VPN acrescentam complexidade. Os webhooks exigem um endpoint público.
Eu precisava de algo mais simples e mais fiável.
A Solução: PostgreSQL como Fila de Comandos
A ideia-chave foi que ambos os lados — a Retool Cloud e o backend local — já partilhavam uma coisa: a base de dados PostgreSQL. Porque não usá-la como message broker?
Retool Cloud NeonDB (PostgreSQL)
│ │
│ INSERT INTO │
│ extraction_command_queue │
│ (type='print', payload=...) │
│ ──────────────────────────────────→ │
│ │
│ │ ← POLL every 5s
│ │ (SELECT ... WHERE
│ │ status='pending')
│ │
│ Local Backend (Windows)
│ │
│ │ Process command
│ │ Download label PDF
│ │ Send to Brother QL-700
│ │ UPDATE status='completed'
│ │
│ ← Query result │
│ (status='completed') │
Os Detalhes de Engenharia
Bloqueio Exclusivo
E se estiverem duas instâncias do backend a correr? Ou se o backend falhar a meio de um comando? Implementei um padrão de bloqueio exclusivo:
- No arranque, o
CommandPolleradquire um bloqueio, escrevendo o seu hostname e PID numa tabelabackend_lock - Antes de processar qualquer comando, verifica se ainda detém o bloqueio
- Se outra instância tentar arrancar, vê o bloqueio e recua
Monitorização por Heartbeat
Um bloqueio sem heartbeat é um deadlock à espera de acontecer. A cada 30 segundos, o backend ativo atualiza o seu timestamp last_heartbeat. Se o heartbeat estiver desatualizado (>60 segundos), qualquer nova instância do backend pode tomar o bloqueio — partindo do pressuposto de que o detentor anterior falhou.
Tipos de Comando
A fila trata de mais do que apenas a impressão:
- Imprimir etiqueta — Descarregar a etiqueta da transportadora a partir da API da Wallapop, gerar o código de barras e enviar para a Brother QL-700
- Imprimir etiqueta LPN — Gerar um código de barras Code128 para um artigo de inventário e imprimir numa etiqueta térmica
- Enriquecer artigo — Acionar o pipeline de enriquecimento por IA (scraping da Amazon → categorizar → gerar descrição)
- Executar extração — Acionar uma extração a pedido de encomendas/conversas/anúncios
Recuperação de Falhas
Os comandos podem falhar (impressora offline, Amazon a bloquear o scraping, timeout de rede). O sistema lida com isto:
- Os comandos falhados são marcados com
status='failed'e uma mensagem de erro - A Retool mostra o erro ao utilizador com um botão "Repetir"
- No arranque, o backend verifica se há comandos órfãos (status='processing' mas com heartbeat desatualizado) e repõe-nos a 'pending'
Porque é que Isto Funciona Melhor do que as Alternativas
- vs. Webhooks: Não é preciso um endpoint público. Sem certificados SSL. Sem DDNS. O backend local é apenas um consumidor a sondar uma base de dados
- vs. Filas de mensagens (RabbitMQ, Redis): Sem infraestrutura adicional. O PostgreSQL já lá está. Para o meu volume (~50 comandos/dia), é perfeito
- vs. WebSocket: Não há uma ligação persistente para manter. Sondar a cada 5 segundos é suficiente para imprimir etiquetas — um atraso de 5 segundos é impercetível
Resultados em Produção
Este sistema está em produção há mais de um ano. Processa comandos de impressão, pedidos de enriquecimento e acionamentos de extração de forma fiável. O failover foi testado (involuntariamente) várias vezes, quando o meu PC local reiniciou — o backend reinicia, reivindica o bloqueio e retoma o processamento em menos de 60 segundos.
O padrão é de código aberto: github.com/AspiranteD/cloud-local-command-bridge