Olá pessoal! Sou o desenvolvedor do Manypost, uma plataforma open-source (AGPL-3.0) multi-tenant de agendamento e publicação multicanal, derivada do Postiz. O nosso repositório ainda é discreto (estamos com cerca de 45 estrelas no GitHub), mas hoje eu gostaria de abrir o capô e compartilhar com vocês um estudo de caso técnico profundo sobre as minhas decisões de arquitetura.
Quando comecei a desenhar a fundação do projeto, eu me vi diante de um dilema: seguir a velha cartilha do ecossistema JavaScript (Node.js + Express + BullMQ para filas) ou apostar em uma arquitetura de 2026, focada em resiliência transacional e Web Standards puros. Escolhi a segunda opção.
Abaixo, detalho as principais decisões arquiteturais que tomei para fazer o sistema agendar e publicar milhares de posts, sem duplicar envio e sem precisar de um emaranhado de microserviços.
Decisão 1: Fila no PostgreSQL (pg-boss) em vez de Redis (BullMQ)
Se você procurar por bibliotecas de fila no ecossistema JS hoje, o BullMQ (apoiado em Redis) é o padrão absoluto para alta performance. No entanto, para o core assíncrono do Manypost, eu escolhi o pg-boss, que roda diretamente no PostgreSQL.
Por que tomei essa decisão?
O problema do "Dual-Write":
Se o meu banco de dados principal já é o Postgres, adicionar o Redis apenas para gerenciar filas críticas gera uma complexidade de infraestrutura desnecessária. Ao usar o pg-boss, o banco de dados relacional atua como Única Fonte da Verdade. Quando um usuário salva um post, eu insiro o registro e enfileiro o job de publicação na mesma transação (ACID). Se um falhar, o outro sofre rollback. Com filas externas, lidar com esse sincronismo é uma dor de cabeça enorme.O poder do
SKIP LOCKED:
Muitos fogem de filas no banco de dados com medo de locks que travam a tabela. O Postgres resolve isso com a instruçãoFOR UPDATE SKIP LOCKED, que o pg-boss utiliza brilhantemente. Isso permite que meus múltiplos workers busquem trabalhos na tabela simultaneamente, sem travar uns aos outros.
O Redis foi abandonado?
Não. Ele ainda existe na minha stack, mas apenas como um coordenador estritamente volátil (ele gerencia as janelas de rate limit das redes sociais, semáforos e realtime via SSE). Se o Redis cair em produção, o realtime sofre degradação, mas o core dos agendamentos continua seguro e rodando no Postgres.
Decisão 2: Web Standards (Hono e Bun) no lugar do Express/Node.js
Enquanto a esmagadora maioria dos backends legados roda em Node.js e Express, eu decidi construir a API do Manypost usando Bun como runtime e Hono como framework web.
Por que eu escolhi o Hono?
O meu objetivo não era apenas velocidade bruta, mas a filosofia do framework. O Hono é baseado inteiramente em Web Standards — ele usa os mesmos objetos Request e Response da API fetch do navegador.
- Footprint e Roteamento: Com menos de 20kB e um roteador super otimizado (
RegExpRouter), o Hono processa a minha API REST e os Webhooks com um overhead quase zero. - Preparado para o Edge: Como eu não dependo das APIs exclusivas do Node.js (
node:http), o código do backend é runtime-agnostic. No futuro, se precisarmos isolar funções da API no Cloudflare Workers ou no Vercel Edge, a fundação arquitetural já está totalmente pronta.
Decisão 3: State Fencing (O Segredo da Não-Duplicação)
Um dos maiores desafios de publicar em redes sociais de forma assíncrona é a duplicação. Mesmo com uma fila robusta como o pg-boss, se a rede der uma engasgada (um timeout na API do X ou LinkedIn, por exemplo), como garanto que o sistema não vai publicar o mesmo post duas vezes no retry?
A solução que implementei foi baseada em Fencing de Estado.
Eu não confio cegamente no que a fila me diz. Antes de disparar a requisição HTTP real para a rede social, o worker é obrigado a reivindicar a posse lógica daquela tentativa em uma tabela dedicada que chamei de publication_attempts.
Nesse processo, o sistema gera um owner_token atrelado a uma job_version.
Se a rede falhar e o pg-boss tentar processar o job novamente por um mecanismo de resiliência, o cursor no banco de dados só avança se o token do worker que pegou o job bater com o token registrado. É um mecanismo de lock pessimista feito inteiramente no nível da lógica de negócio.
Resumo das minhas decisões
É muito comum desenvolvedores acharem que, para criar um sistema escalável, precisam começar desde o dia zero com Kafka, Kubernetes e bancos NoSQL. O Manypost foi desenhado para provar o oposto.
| Padrão da Indústria | Minha Decisão no Manypost | Por que funciona melhor aqui? |
|---|---|---|
| Microserviços (Rede complexa) | Monorepo (API + Worker + Web) | Tipagem end-to-end garantida (packages/contracts) e deploy flexível (MODE=all para rodar tudo no mesmo processo até que precisemos escalar). |
| Redis para Filas Críticas | PostgreSQL (pg-boss) | Consistência transacional (evita dual-write) e uso inteligente de SKIP LOCKED. Zero perda de dados no background. |
| Node.js + Express / NestJS | Bun + Hono | Altíssima performance, totalmente focado em Web Standards, e desacoplado do Node.js para rodar no Edge. |
| RLS (Row Level Security) | Joins controlados na Aplicação | O isolamento Multi-Tenant é responsabilidade dos Repositories. Obrigo junções com a tabela pai para evitar vazamento de dados, mantendo o esquema de banco simples. |
Conclusão
Construir o Manypost está sendo um laboratório incrível de decisões que fogem do "senso comum", mas que entregam uma estabilidade absurda para um sistema concorrente.
Gostaria muito de saber o que vocês acham dessa abordagem técnica! Se quiserem ver de perto como estruturei esse Fencing de estado ou como configurei o Monorepo com Bun, convido todos a explorarem a documentação oficial e o código-fonte diretamente no repositório do projeto no GitHub. Qualquer dúvida, fiquem à vontade para perguntar nos comentários!
Top comments (0)