5

Estudo de Caso: Por que ignorei o padrão da indústria ao arquitetar o Manypost

Olá guys! 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 mais algumas decisões e compartilhar com vocês um estudo de caso técnico profundo sobre essas 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?

1. 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.

2. 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ção FOR 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ústriaMinha Decisão no ManypostPor 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íticasPostgreSQL (pg-boss)Consistência transacional (evita dual-write) e uso inteligente de SKIP LOCKED. Zero perda de dados no background.
Node.js + Express / NestJSBun + HonoAltíssima performance, totalmente focado em Web Standards, e desacoplado do Node.js para rodar no Edge.
RLS (Row Level Security)Joins controlados na AplicaçãoO 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!

Carregando publicação patrocinada...