Monólito ou microsserviços vistos pelo banco de dados
Trate monólito ou microsserviços como decisão sobre propriedade dos dados: mapeie transações, exponha tabelas compartilhadas e separe limites reais.

Um limite de serviço só é confiável quando os dados de cada lado podem mudar sem um commit coordenado no banco. Se dois trechos de código precisam bloquear as mesmas linhas, atualizar as mesmas tabelas ou combinar a mesma janela de implantação, traçar uma linha HTTP muda o transporte, não a arquitetura.
Por isso a comparação útil entre monólito e microsserviços começa no esquema. O código pode ir para trás de um endpoint em uma tarde. Propriedade, invariantes, dados históricos, consultas de relatórios, retentativas e recuperação continuam ligados às tabelas. Equipes que começam por classes e pacotes costumam perceber isso depois de criar um monólito distribuído: mais chamadas de rede, mais trabalho operacional e o mesmo acoplamento de banco por baixo.
Não sou contra microsserviços. Sou contra fingir que um limite de processo cria um limite de dados. O esquema mostra onde o sistema já funciona como uma unidade, onde divide armazenamento por conveniência e onde uma separação sobreviveria a uma implantação ruim às duas da manhã.
Um mapa de transações vale mais que um grafo de dependências
Mapeie cada operação de negócio para as linhas que ela lê e grava antes de escolher limites. Um grafo de código mostra qual módulo chama outro. Não mostra que lançar uma fatura, reservar estoque e gravar auditoria precisam funcionar ou falhar juntos. O banco sabe disso porque as gravações compartilham uma transação.
Comece pelas operações, não pelas tabelas. Para cada comando que muda estado, registre ator, tabelas lidas e gravadas, bloqueios, restrições usadas e consequência de execução parcial. Inclua jobs, triggers, procedimentos, importações e scripts de operadores. É comum o suposto limite desaparecer neles.
Um mapa compacto pode registrar Fazer pedido como leitura de cliente, produto e estoque e gravação de pedido, item e estoque. A invariante impede estoque negativo; uma falha parcial deixa um pedido aceito que não pode ser atendido. Capturar pagamento lê pedido e tentativas anteriores e grava pagamento, lançamento contábil e estado sob a regra de uma captura por pedido. Cancelar pedido toca remessa, pagamento, estoque, reembolso e pedido porque um item enviado precisa de caminho de devolução.
O mapa expõe dois tipos de acoplamento. O transacional exige que gravações confirmem juntas para preservar uma invariante. O de leitura significa que uma operação consulta dados de outro dono. O primeiro pode impedir a separação. O segundo costuma exigir réplica, projeção alimentada por eventos ou pedido explícito, mas não propriedade compartilhada. Equipes confundem ambos e deixam tudo junto para sempre ou distribuem uma transação que não precisava disso.
Rastreie consultas de produção além do código. SQL dinâmico, instruções do ORM, jobs noturnos e procedimentos escapam da análise estática. Uma tabela sem referências óbvias ainda pode alimentar o fechamento mensal. Se remover um módulo deixa o relatório da contabilidade errado três dias depois, ele não está isolado.
Tabelas compartilhadas adiam o custo de coordenação
Uma tabela compartilhada permite entregar rápido ao transformar o banco em API privada de integração. Faturamento insere uma linha, relatórios lê diretamente e atendimento adiciona uma coluna de estado. Nada parece caro até uma equipe mudar uma coluna, reinterpretar valor, preencher linhas antigas ou restaurar backup. Então todo leitor participa da mudança.
O custo não vem de dois processos poderem consultar a mesma tabela. Vem da autoridade ambígua. Qual serviço adiciona uma restrição? Quem decide se status = 4 significa embalado ou enviado? Qual implantação controla o backfill? Quem restaura se um serviço precisa voltar a um ponto e outro já gravou estado novo? Armazenamento compartilhado transforma trabalho normal de esquema em agenda entre equipes.
Chaves estrangeiras exigem distinção precisa. Dentro de um limite de propriedade, são documentação executável útil. Atravessando futuros serviços, indicam que o banco ainda impõe uma invariante entre eles. Removê-las não remove a regra; só leva a detecção ao código e permite referências ruins. Mantenha os sistemas juntos até dizer o que substitui a garantia.
Esta consulta de propriedade é um bom começo no PostgreSQL:
SELECT table_schema, table_name,
array_agg(DISTINCT application_name ORDER BY application_name) AS writers
FROM audit_statement_usage
WHERE command IN ('INSERT', 'UPDATE', 'DELETE')
GROUP BY table_schema, table_name
HAVING count(DISTINCT application_name) > 1
ORDER BY table_schema, table_name;
PostgreSQL não traz audit_statement_usage. Crie a tabela a partir de logs de auditoria ou telemetria com pelo menos application_name, comando, esquema e tabela. O formato da saída é o ponto: toda linha com vários escritores exige decisão de propriedade, não endpoint. Não deduza propriedade só por papéis se aplicações compartilham credenciais, pois isso inutiliza a evidência.
O alvo saudável tem um escritor autoritativo por tabela. Outros componentes recebem cópias feitas para suas consultas. Uma cópia tem contrato de atualização e pode ser reconstruída. Uma tabela compartilhada esconde várias partes dependentes da forma atual, contrato muito mais difícil de enxergar.
A exigência de consistência escolhe o limite
Mantenha dados na mesma transação quando o negócio não tolera estado intermediário visível. Separe quando o acordo atrasado é aceitável e existe reparo definido. É decisão de produto expressa no banco, não preferência por código síncrono ou assíncrono.
Pagamento e contabilidade deixam isso claro. Se o sistema registra uma captura sem o lançamento correspondente, mesmo por pouco tempo, outro job pode reembolsar, liquidar ou informar errado. É possível separar as gravações, mas será preciso protocolo explícito para intenção atômica, retentativas, deduplicação e conciliação. A rede transformou invariante local em fluxo distribuído. Pode ser justificável, mas não é desacoplamento grátis.
A recomendação popular de colocar broker entre tudo está errada quando vem antes da decisão de consistência. Um broker transporta mensagens sob condições declaradas. Não decide quanto uma reserva pode atrasar, o que fazer com duplicata nem quem repara projeção ausente. Isso é semântica da aplicação. Escondê-la em publish() reduz a evidência.
Faça quatro perguntas por limite:
- Que estado usuários ou jobs observam entre os commits?
- Por quanto tempo ele pode ficar inconsistente?
- Qual lado tenta de novo e como o receptor reconhece duplicata?
- Que processo detecta e repara mensagem que não produziu o estado esperado?
Se a segunda resposta é praticamente zero, prefira uma transação, salvo quando isolamento regulatório, escala ou propriedade organizacional justifiquem coordenação distribuída. Se forem segundos ou minutos, escreva o limite e monitore. A palavra eventual não é objetivo de serviço.
Um banco não significa um único dono
É possível impor propriedade real em um banco separando esquemas, papéis, migrações e escrita. No sentido inverso, servidores separados seguem acoplados quando serviços coordenam versões e fazem chamadas síncronas para reconstruir joins antigos. Separação física só prova um limite quando traz independência operacional.
Uma transição útil dá a cada componente um esquema e login que só grava nele. Leituras entre esquemas são exceções temporárias inventariadas. Permissões transformam gravações acidentais em falhas visíveis:
REVOKE ALL ON SCHEMA billing FROM fulfillment_app;
GRANT USAGE ON SCHEMA billing TO fulfillment_app;
GRANT SELECT ON billing.invoice_summary TO fulfillment_app;
REVOKE INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA billing FROM fulfillment_app;
Dê acesso a uma view específica como invoice_summary, não a todas as tabelas. Ela oferece pequena superfície de compatibilidade enquanto consumidores migram para API ou projeção local. Registre cada concessão com dono e condição de remoção. Caso contrário, a ponte temporária vira o próximo banco compartilhado.
Database-per-service também é mal interpretado. Significa que um serviço controla a persistência e outros não podem contorná-la. Não exige um cluster para cada processo pequeno. Bancos lógicos separados ajudam em restauração e permissões, enquanto esquemas numa instância PostgreSQL podem bastar na extração. Escolha isolamento que imponha propriedade sem multiplicar operação antes de provar o limite.
Todo join entre limites precisa de substituto definido antes da extração. Um join local filtra, ordena e pagina numa fotografia consistente. Várias APIs podem buscar milhares de registros, criar N-mais-um e combinar resultados de momentos diferentes. O endpoint pode devolver JSON certo no teste e falhar com cardinalidade real.
Escolha conforme a consulta. Uma tela que precisa de status atual pode fazer pedido direto com timeout e alternativa definidos. Busca que filtra pedidos por atributos de cliente costuma precisar de projeção local. Relatório offline pertence a armazenamento analítico. Copiar fatos selecionados é duplicação deliberada; empilhar chamadas até criar join distribuído por acidente é acoplamento oculto.
Paginação revela falha comum. Se o cliente pede os primeiros 50 pedidos por risco, mas pedido e risco têm donos distintos, buscar 50 e depois o risco não gera os 50 corretos. Buscar mais é palpite de desempenho instável. Mova o ranking para um modelo de leitura ou mude o contrato. Fan-out de rede não preserva semântica por otimismo.
Defina atualização conforme a decisão. Um bloqueio antifraude pode exigir estado atual e fechar quando o dono não responde. Um painel comercial pode aceitar minutos de atraso. Guarde versão observada ou horário para o cliente aplicar a regra. Sem procedência, cache parece atual mesmo com feed parado há horas.
Não divida banco apenas porque uma tabela é grande. Particionamento, índices, arquivo e isolamento resolvem armazenamento e consulta diretamente. Um limite paga seu custo quando separa autoridade de mudança ou comportamento de falha. Tamanho diz pouco sobre isso.
Eventos precisam de uma fonte atômica
Use outbox transacional quando uma mudança confirmada deve gerar evento com segurança. Gravar dados e publicar depois deixa lacuna: commit funciona e publicação falha. Publicar antes cria a lacuna inversa. Transação distribuída pode fechar, mas aumenta acoplamento e raramente tem bom suporte entre todos os sistemas.
A outbox põe a mudança e o evento no mesmo commit local:
BEGIN;
UPDATE orders
SET status = 'confirmed', version = version + 1
WHERE id = :order_id AND status = 'pending';
INSERT INTO outbox_event (event_id, aggregate_id, event_type, payload, created_at)
VALUES (:event_id, :order_id, 'order.confirmed', :payload, CURRENT_TIMESTAMP);
COMMIT;
Um relay publica linhas pendentes e marca progresso. Pode publicar duas vezes se cair após enviar e antes de registrar sucesso, então consumidores precisam de idempotência. Guarde ID estável e condicione a mudança a ele não ter sido processado. Promessas exactly-once costumam ser entrega at-least-once com efeitos deduplicados. Diga o que realmente oferece.
Ordem também conta. Uma sequência global limita vazão e cria acoplamento falso; sem regra, cancelamento pode ultrapassar confirmação. Ordene por agregado onde necessário, inclua a versão e rejeite ou estacione lacunas. O reparo deve ser simples para um operador executar sem inventar estado.
Captura de mudanças pode alimentar a outbox, mas mudança bruta de tabela não substitui evento de domínio. Atualização de linha diz que armazenamento mudou, não se pedido foi confirmado, corrigido, importado ou reparado. Consumidores que deduzem intenção por colunas ficam presos ao esquema que deveria ser liberado.
Relatórios expõem propriedade que comandos escondem
Limites operacionais raramente combinam com perguntas analíticas. Relatórios devem combinar cópias próprias, não recuperar acesso a toda tabela. Um relatório de rentabilidade pode exigir pedidos, reembolsos, suporte e contabilidade. Fazer chamadas síncronas a quatro serviços recria join distribuído com pior latência e falhas.
Crie armazenamento de relatórios por eventos, mudanças ou extrações, com regras de atualização e conciliação. Ele pode desnormalizar porque não possui a verdade operacional. Quando definição mudar, reconstrua a projeção por fatos retidos ou fotografia controlada em vez de exigir que fontes preservem cada consulta histórica.
Cuidado com customer. Identidade, pagador, destinatário, login e parte jurídica começam como uma linha e divergem. Um serviço universal pode virar dependência de quase tudo. Defina fatos de cada domínio e identificadores que os unem. Duplicar nome de exibição costuma ser mais barato que exigir disponibilidade síncrona do perfil central.
Conciliação responsabiliza consistência eventual. Compare contagens e totais por chaves estáveis, acompanhe o evento pendente mais antigo e mantenha replay. Painel verde do broker não prova que relatórios batem com o razão. Compare resultados de negócio, não atividade de transporte.
Em ambiente regulado, cópias também afetam retenção, acesso e exclusão. Projeção continua sendo dado. Inventarie o fluxo de campos sensíveis, minimize o recebido e prove exclusão ou retenção nas réplicas. Separar armazenamento não elimina governança.
Uma extração segura move propriedade antes do tráfego
Mova uma capacidade estabelecendo contrato de dados, observando comportamento em sombra e transferindo escrita antes de rotear todo o tráfego. Extrair código primeiro deixa o serviço dependente das tabelas antigas e leva o maior risco para o fim sob pressão.
Uma sequência confiável é:
- Escolha capacidade com dono plausível e limite tolerável. Inventarie leitores e escritores.
- Cerque o comportamento atual com testes, incluindo falhas, tentativas, arredondamento, nulos e intervenção de operador. Capture pedidos representativos apenas sob regras de privacidade.
- Introduza o esquema futuro e preencha com jobs repetíveis e checkpoints. Leia em sombra e compare sem servir.
- Passe a um caminho autoritativo. Se dupla escrita temporária for inevitável, registre resultados e concilie; não suponha igualdade.
- Roteie leituras e tráfego ao novo dono, mantenha rollback testado e retire permissões só quando atraso e paridade ficarem nos limites.
O difícil é backfill com mudanças ao vivo. Uma fotografia começa enquanto produção continua. Use snapshot consistente e posição de mudança, depois aplique alterações em ordem. Faça o preenchimento idempotente. Registre rejeições com contexto; migração que pula dado antigo malformado cria esquema limpo e registro de negócio falso.
Evite sincronização bidirecional. Parece rollback seguro, mas regras de conflito viram outra aplicação. Nomeie uma autoridade por campo e fase. Rollback deve reverter roteamento e reproduzir log conhecido, não deixar ambos editarem o mesmo fato.
Uma fachada strangler ajuda a rotear, mas não resolve propriedade. Se manda pedidos para serviço que ainda atualiza tabelas do monólito, só mudou topologia. Meça progresso por escritores aposentados, permissões cruzadas removidas e migrações coordenadas eliminadas.
Testes de paridade devem comparar efeitos de negócio
Teste reescrita por comportamento observável e estado resultante, não por status HTTP parecido. Sistemas antigos guardam regras em triggers, jobs, padrões, truncamento, collation, fusos e reparos manuais. Uma reescrita limpa pode parecer lógica e mudar faturas ou estoque.
Crie harness que envia o mesmo pedido gravado ou sintético aos caminhos, normaliza diferenças permitidas e compara respostas e efeitos no banco. Em comandos, compare linhas, dinheiro, eventos e erros. Em consultas, ordem, paginação, nulos e permissões. Mascare dados sensíveis antes e guarde o resultado como evidência.
Defina tolerâncias por campo. Horários podem variar numa janela. IDs podem diferir preservando relações. Dinheiro decimal costuma exigir igualdade exata após a regra de arredondamento. Um diff JSON geral cria ruído até ser ignorado.
Execute falhas. Mate o relay após publicar, repita timeout, entregue eventos fora de ordem, restaure snapshot e rode consumidor antigo com evento novo. O limite é confiável quando falhas têm efeitos contidos e reparo documentado, não quando o diagrama parece limpo.
É aí que a abordagem da CodeHero importa: ela lê todo o código, moderniza a arquitetura e compara comportamento com tráfego gravado num harness de paridade. A entrega em menos de 30 dias seria imprudente se efeitos no banco não fossem comportamento e os arquivos fossem apenas traduzidos.
Mudanças compatíveis permitem implantações independentes
Um limite não é implantável sozinho se toda mudança obriga produtores e consumidores a trocar juntos. Projete banco e eventos para versões novas e antigas coexistirem por período definido. A sobreposição permite implantar, observar e voltar sem convocar todos.
Use expand and contract. Adicione o campo novo sem remover o antigo. Faça escritores preencherem o novo, complete histórico e deixe leitores preferirem o novo aceitando o antigo. Verifique que nenhum leitor restou, pare de produzir o campo e remova depois. Cada fase precisa de saída mensurável, como zero leituras por um ciclo, não palpite de calendário.
Renomear coluna no lugar parece limpo, mas quebra binários antigos, jobs esquecidos e rollbacks. Adicione o nome, copie sob uma autoridade e contraia depois. A coluna extra é complexidade temporária com retirada; interrupção coordenada é complexidade sem plano.
Esquemas de eventos exigem a mesma disciplina. Consumidores devem ignorar campos desconhecidos, produtores não mudar significados e campos obrigatórios ter introdução válida. Versione o contrato semântico, não toda adição. Publicar order.cancelled.v2 para cada atributo opcional enche o sistema de conversões sem proteger do perigo: redefinir cancelled.
Views dão janela curta para leituras renomeadas. São APIs permanentes ruins se consumidores dependem de joins ou planos não documentados. Defina expiração e observe quem consulta. Remover por busca no repositório ignora relatórios ad hoc e binários externos.
Rollback testa a honestidade. Se o app novo grava valor que o antigo não lê, voltar o binário não restaura serviço. Teste código antigo contra estado novo, incluindo enums, nulidade, strings maiores e variantes. DDL compatível é metade; dados devem continuar legíveis.
Implantação independente exige evidência. Mostre que cada versão funciona com cada fase, backfill continua e telemetria identifica consumidores. Se a matriz falha, os serviços dividem o mesmo trem de versões mesmo com nomes distintos.
Alguns monólitos devem continuar monólitos
Mantenha o monólito quando uma equipe muda tudo de forma coerente, transações combinam com invariantes, risco está controlado e escala não exige colocação independente. Um monólito modular com propriedade imposta oferece quase toda a clareza sem falhas de rede nem frota operacional.
A razão mais forte para separar é autoridade independente sustentada por limite real de dados. Isolar carga instável, perímetro de segurança ou computação com escala diferente também serve. Nada disso perdoa consistência indefinida, mas pode justificar o custo.
Não use fórmulas de tamanho de equipe. Duas equipes colaboram bem num repositório e uma pode criar coleção ruim de serviços mínimos. Organização influencia o design, mas o banco impõe os fatos. Se toda versão exige migração coordenada, não há entrega independente.
Antes de aprovar, exija registro com tabelas próprias, escritor autoritativo, leituras cruzadas, janela de consistência, contrato de evento, unidade de restauração, sequência e autoridade de rollback. Rejeite ambos os serviços ou eventually sem limite e reparo. Esse padrão evita mais dano que discutir a quantidade ideal.
Trate restauração e desastre como testes de limite. Se um serviço não restaura sem rebobinar outro, dividem unidade operacional. Se replay exige edição não documentada em tabela alheia, a propriedade está incompleta. Pratique com eventos em fila e projeções. Confirme que consumidores rejeitem replays antigos e reconstruam cópias sem gravar no esquema restaurado; registre quem decide o ponto e como escritas posteriores retornam.
A primeira mudança útil costuma ser menor que uma extração: elimine credenciais compartilhadas, registre escritores por aplicação, dê dono a toda tabela e exponha acesso cruzado. Quando o esquema mostrar limites honestos, o código segue. Onde o esquema não se separar, escute-o.
Perguntas frequentes
Cada microsserviço deve ter seu próprio banco?
Cada microsserviço deve controlar sua persistência e impedir que outros contornem o contrato com gravação direta. Isso pode significar bancos separados, mas esquemas e papéis num banco podem isolar o bastante durante a migração.
Banco compartilhado é sempre ruim para microsserviços?
Servidor compartilhado não é ruim por si; autoridade de escrita compartilhada é. Se serviços possuem esquemas separados e usam contratos explícitos, um servidor pode ser solução transitória ou permanente.
Como encontro limites de transação num monólito?
Siga cada comando até todas as linhas lidas e gravadas, incluindo triggers, jobs, procedimentos e scripts. Agrupe mudanças que precisam confirmar juntas para manter uma invariante de negócio.
Uma API remove acoplamento de banco?
Ela esconde o esquema, mas não remove acoplamento quando clientes ainda exigem commits, versões ou recuperação coordenados. O limite melhora quando o provedor possui os dados e clientes aceitam seu contrato.
Quando devo usar consistência eventual?
Quando o negócio aceita um período nomeado de diferença e a equipe tem retentativa, deduplicação, monitoramento e reparo. Se o atraso aceitável é zero, transação local costuma ser mais clara.
Qual a forma mais segura de dividir tabela compartilhada?
Escolha um escritor, crie esquema próprio, preencha desde uma posição consistente e aplique mudanças seguintes. Leituras em sombra e conciliação devem provar paridade antes da troca.
Um broker resolve transações distribuídas?
Não. Ele transporta mensagens, mas a aplicação define estados parciais, duplicatas, ordem e reparo. Uma outbox fecha a lacuna entre commit e evento; consumidores ainda precisam de idempotência.
Como relatórios funcionam após dividir o monólito?
Alimente armazenamento separado com eventos, mudanças ou extrações controladas. Dê regras de atualização e conciliação em vez de consultar todo banco operacional ou juntar serviços ao vivo.
O que um rollback de microsserviço deve incluir?
Roteamento, compatibilidade do esquema, eventos na fila e gravações aceitas pela versão falha. Mantenha uma autoridade por campo e reproduza log conhecido; escrita bidirecional complica conflitos.
Monólito modular é melhor que microsserviços?
É melhor quando transações, propriedade da equipe e implantação ainda andam juntas. Módulos e permissões impostas dão propriedade clara sem chamadas de rede, recuperação distribuída e operação separada.