A mensagem no Slack chegou às 22h47 de uma sexta. Um tester tinha pago no sandbox do Stripe, o webhook disparou, a linha de assinatura apareceu no banco, e o web app ainda dizia "Plano gratuito."
Eu sabia onde olhar. Tinha escrito a feature três semanas antes e ainda lembrava o formato dela. O que não lembrava, porque ninguém lembra até doer, é que a palavra na tela e o valor na API não ficam alinhados sozinhos.
O bug nunca esteve em um repositório só
A API guardava o status do plano como um enum inteiro. O web app mapeava esse inteiro para um rótulo por um arquivo de constantes compartilhado. O admin panel tinha sua própria coluna com um badge legível.
Os três estavam certos na segunda. Na sexta alguém tinha adicionado um estado de trial novo na migration da API, atualizado o texto do web app e esquecido a cor do badge no admin. Três edições pequenas em três lugares que deveriam representar o mesmo fato.
Num setup multi-repo isso são três pull requests, três revisões se você tiver sorte, três pipelines de deploy e uma janela em que produção mostra três verdades diferentes dependendo da URL que você abre. Num sexta à noite você não tem paciência para essa janela. Você tem um tester atualizando a página de cobrança e se perguntando se o produto funciona.
No CastorStack as quatro aplicações vivem em um monorepo: marketing site, web app do cliente, admin panel e API em .NET. Os contratos compartilhados ficam em shared/. O nome do produto e a lista de locales ficam em config/. Quando o enum muda, TypeScript e C# veem a mesma fonte de verdade num commit só.
O conserto daquela sexta foi um branch, um diff, um push. A API devolveu o estado novo, o web app leu o mapeamento compartilhado, o admin panel pegou o mesmo nome de enum. Sem quebra-cabeça de ordem de deploy. Sem "a Vercel terminou antes do Azure?"
O que as pessoas temem em monorepos
A objeção que mais ouço é ruído. Você muda a cor de um botão no hero do marketing e o CI roda testes do admin panel. Preocupação justa, e também resolvida se o pipeline for escopado.
O CI do CastorStack não trata todo arquivo igual. Uma mudança em apps/web-app/ não precisa bloquear um spec do marketing site que não toca contratos compartilhados. O objetivo não é um job gigante que roda para sempre. O objetivo é um lugar só onde você consegue buscar no produto inteiro quando um bug cruza fronteiras.
A segunda objeção é controle de acesso. "Contratados de marketing não deveriam ver código de billing." Verdade em times grandes. Para um fundador solo ou uma dupla, todo mundo já vê tudo porque todo mundo é todo mundo. Separar repositórios antes de ter um problema de permissão compra pureza organizacional ao preço de coerência numa sexta à noite.
A terceira é medo de ferramenta: "Monorepo precisa de Nx ou Turborepo ou script custom." Às vezes precisa em escala. Na escala de lançamento você precisa de um package.json na raiz que saiba rodar cada app, disciplina de um lockfile só e o hábito de colocar tipos compartilhados numa pasta em vez de copiar interface. O CastorStack já traz esse layout para você não inventar na semana dois.
O momento em que o monorepo se paga
Nunca é no primeiro commit. O primeiro commit parece pesado porque a árvore de pastas é grande.
Ele se paga na primeira vez que uma feature toca mais de uma superfície. Billing toca o handler de webhook na API, a página de assinatura do cliente, a lista de assinaturas no admin e, muitas vezes, uma linha de copy na tabela de preços do marketing. Autenticação toca cada app mais configuração de CORS mais templates de e-mail.
Conte as superfícies. Esse é o número de repositórios que você se oferece para manter alinhados se separar cedo.
Um monorepo não elimina coordenação. Ele move a coordenação para o histórico do Git em vez do seu calendário. Você vê, num diff, que o handler do webhook e o texto da UI mudaram juntos. Revisão de código, até auto-revisão na manhã seguinte, pega o drift antes dos usuários.
O que um monorepo não te dá
Não substitui decisão de produto. Você ainda escolhe se usuários em trial veem features premium. Ainda escolhe se o admin pode cancelar assinatura em nome do cliente. A forma do repositório não responde essas perguntas. Só deixa visíveis num lugar só as consequências das suas respostas.
Também não substitui independência de deploy. O CastorStack faz deploy do marketing site na Vercel, das duas apps Vite em projetos Vercel próprios e da API no Azure Container Apps. Mesmo repo, quatro destinos de deploy. O monorepo é alinhamento de código-fonte, não um binário único.
E não é complexidade grátis para sempre. Se você crescer para times separados com trens de release distintos, tirar um bounded context pode fazer sentido. A maioria dos produtos nunca chega nesse dia. A maioria chega no dia em que um usuário pagante encontra discrepância entre o que a fatura diz e o que o dashboard mostra.
Por que isso importa agora que construir ficou barato
Gerar uma página React ou um controller em C# leva minutos agora. O caro é ligar a página, o controller, a migration, a view do admin e o copy do e-mail para que continuem significando a mesma coisa depois da terceira iteração.
Um modelo escreve cada arquivo isolado. Sozinho, ele não garante que esse isolamento sobreviva ao contato com produção. Um monorepo com contratos compartilhados é como você dá ao modelo, e a si mesmo, um mapa só.
O CastorStack existe para você começar desse mapa: quatro apps, configuração compartilhada, testes e CI que já conhecem os limites. O conserto da sexta que descrevi não é argumento de venda porque seja glamouroso. É porque é trabalho ordinário que fica caro quando o layout do repositório briga com você.
Se você está escolhendo onde gastar as semanas do lançamento, gaste na feature que só importa para seus clientes. Deixe o repositório defender a coerência sozinho.