Primeiro, decida se você deve migrar
Microsserviços não são um objetivo. São um trade-off. Eles adicionam complexidade operacional, latência de rede, modos de falha de sistemas distribuídos e custo de coordenação organizacional. Em troca, podem destravar deploy independente, escalabilidade isolada, heterogeneidade tecnológica e autonomia de times. Antes de desenhar qualquer diagrama de decomposição, pergunte-se honestamente se você realmente precisa desses benefícios.
O teste é simples. Olhe para a fricção que o monolito está causando hoje. Os deploys estão ficando lentos? Times estão bloqueados pelo calendário de release uns dos outros? Partes do sistema precisam escalar de forma diferente das outras? Existe uma parte do código que deveria evoluir com outra stack? Se a resposta for não, o monolito provavelmente está bem. Um monolito bem modularizado, com fronteiras internas limpas, supera por anos um sistema distribuído mal projetado.
Se a resposta for sim, microsserviços são uma resposta válida, mas não a única. Um monolito modular, com fronteiras estritas, artefatos de deploy separados e propriedade clara, costuma entregar boa parte do benefício a uma fração do custo. Já levamos vários times para esse passo intermediário antes de decidir se a decomposição completa era mesmo necessária. Comprou clareza sem comprar complexidade.
Encontre os bounded contexts antes de nomear os serviços
O maior erro em migrações de microsserviços é dividir por preocupação técnica ("user-service", "auth-service", "database-service") em vez de por preocupação de negócio. Serviços que compartilham um domínio se misturam para sempre. A unidade certa de decomposição é o bounded context do Domain-Driven Design: um pedaço do negócio com sua própria linguagem, suas próprias regras, seu próprio modelo.
Em uma empresa de logística, os contextos podem ser "shipment", "billing", "warehouse", "route planning" e "customer notifications". Em uma plataforma de varejo, podem ser "catálogo", "preço", "estoque", "checkout" e "fulfillment". Os nomes certos vêm do negócio, não do código. Se dois times descrevem a mesma entidade de formas levemente diferentes, isso costuma ser um sinal de dois contextos escondidos dentro de um mesmo modelo.
Um exercício prático para começar é event storming: reunir os engenheiros e as pessoas mais próximas do negócio em uma sala (presencial ou virtual) e percorrer o ciclo de vida de um evento real de cliente. Liste os eventos de domínio ("PedidoColocado", "PagamentoAutorizado", "EnvioDespachado") e agrupe-os. Os clusters naturais costumam ser os seus futuros serviços.
Sinais de que você encontrou um bounded context real
- Ele tem regras de negócio claras que o resto do sistema não precisa conhecer.
- Tem o seu próprio vocabulário. A mesma palavra significa coisas levemente diferentes fora dele.
- Pode publicar eventos para que outros contextos reajam, em vez de ser chamado sincronamente o tempo todo.
- Um único time conseguiria operar o contexto ponta a ponta sem coordenar diariamente com todos os outros.
Aplique o padrão strangler fig em vez de uma reescrita grande
O clássico padrão strangler fig, batizado por Martin Fowler, é a forma mais segura de migrar um monolito. A ideia é simples: você não reescreve o sistema. Você coloca uma camada de roteamento na frente dele e vai descascando uma capacidade de cada vez para um novo serviço. Com o tempo, o novo sistema cresce, o monolito encolhe, até que o monolito seja desativado.
Isso funciona porque cada passo entrega valor. Você nunca carrega uma reescrita de vários trimestres nas costas. Nunca diz para o negócio "espere doze meses e você verá algo." Cada incremento está em produção, observável, reversível.
Na prática, o strangler fig começa com um API gateway, um proxy reverso ou um feature flag na frente da aplicação existente. Novas rotas vão para o novo serviço; as rotas existentes continuam batendo no monolito. Conforme a confiança cresce, mais rotas migram. Se o novo serviço se comportar mal, o tráfego volta ao monolito por configuração, não por release.
A primeira capacidade a extrair raramente é a mais óbvia. Resista à tentação de começar pela "maior bagunça". Comece por algo com bounded context claro, raio de explosão pequeno se falhar e valor de negócio claro quando extraído. Notificações, busca, recomendações, audit logs e relatórios são bons primeiros candidatos.
A parte mais difícil são os dados
Código é a parte fácil da decomposição. Dados são onde os projetos travam. Um banco de dados compartilhado é o antipadrão mais comum: dois serviços que supostamente cuidam de domínios diferentes, mas secretamente fazem join nas mesmas tabelas, derrotam o propósito da decomposição. Eles vão fazer deploy juntos. Vão falhar juntos. Vão evoluir juntos. Eles não são microsserviços.
O princípio é que cada serviço deve ser dono dos seus dados. Outros serviços lêem via API ou eventos, não invadindo o banco. Isso é desconfortável no começo porque o monolito provavelmente tem centenas de joins entre o que deveriam ser domínios diferentes. Desembaraçar isso é o trabalho real.
Um padrão útil é o "expand, migrate, contract": primeiro expanda o modelo de dados adicionando o novo formato lado a lado com o antigo. Depois migre os consumidores um a um para o novo formato. Por fim, contraia, removendo a estrutura antiga. Em cada passo, o sistema continua funcionando. Nada é reescrito de uma vez só.
Para sistemas de alto volume, event-carried state transfer costuma ser a forma mais limpa de manter os serviços independentes sem cargas batch noturnas. O serviço dono publica eventos sempre que seus dados mudam; os serviços consumidores mantêm sua própria cópia local atualizada. Isso é assíncrono, escalável e resiliente a indisponibilidades, mas exige pensamento cuidadoso sobre ordem, idempotência e consistência eventual.
A lei de Conway é invicta
"Organizações desenham sistemas que espelham a sua estrutura de comunicação." Essa é a lei de Conway, e é a força mais subestimada em migrações para microsserviços. Se você divide o sistema em dez serviços, mas mantém um único time dono de tudo, você tem um monolito distribuído. Pior: você assume toda a complexidade operacional dos microsserviços sem nenhum dos benefícios de autonomia.
Uma decomposição bem-sucedida quase sempre vem acompanhada de mudança organizacional. Cada bounded context deveria mapear para um time capaz de operá-lo ponta a ponta: design, desenvolvimento, deploy, monitoração e on-call. Se o time é pequeno demais para sustentar isso, o contexto provavelmente é pequeno demais. Se o time é grande demais para se coordenar rápido, o contexto provavelmente é grande demais.
Aqui é onde liderança faz diferença. A migração não dá certo sem decisões explícitas sobre propriedade, responsabilidade e investimento compartilhado em plataforma. Sem isso, cada time refaz o mesmo encanamento, cada serviço reinventa observabilidade e o custo explode.
Métricas para medir o sucesso da migração
Migrações para microsserviços devem ser acompanhadas por métricas de engenharia, não por número de serviços. Número de serviços não é objetivo. Objetivo é entrega mais rápida, melhor confiabilidade e propriedade mais clara. Indicadores úteis incluem:
- Frequência de deploy. Serviços independentes estão deployando várias vezes por semana, ou ainda estão presos a um release trimestral?
- Lead time de mudança. Do commit até produção, quanto tempo leva uma correção pequena?
- Taxa de falha em mudanças. Que percentual dos deploys precisa de rollback ou hotfix?
- Tempo médio de recuperação. Quando algo quebra, em quanto tempo o time detecta e resolve?
- Indicadores de acoplamento. Com que frequência uma mudança em um serviço exige deploy coordenado com outro? Esse número deve cair ao longo do tempo.
Essas métricas, popularizadas pelas pesquisas da DORA, são proxies confiáveis para saber se a decomposição está pagando. Se elas melhoram, a migração está saudável. Se não, mais serviços não vão resolver o problema.
Erros comuns que vemos em migrações reais
Ao longo de muitos projetos de modernização, os mesmos erros aparecem repetidamente. Vale citá-los para que você consiga identificá-los no seu próprio programa.
Dividir por tabela
Cortar serviços por tabelas de banco em vez de capacidades de negócio. Você acaba com serviços anêmicos que precisam chamar uns aos outros para fazer qualquer coisa útil.
Banco compartilhado
Dois serviços lendo e escrevendo nas mesmas tabelas. Você não decompôs nada; apenas adicionou network hops.
Monolito distribuído
Serviços que precisam ser deployados juntos. O pior dos dois mundos: complexidade de sistemas distribuídos com acoplamento de monolito.
Reescrita big bang
Construir a nova plataforma em paralelo por um ano antes de migrar. Concentra risco no cutover. Use strangler fig.
Sem investimento em plataforma
Cada time constrói seu próprio logging, CI, segredos e tracing. O custo de operar dez serviços vira insuportável.
Sem propriedade clara
Serviços sem time dono viram órfãos, depois incidentes, depois débito técnico que ninguém quer tocar.
Resumo
Uma boa migração não é medida por quantos serviços existem ao final. É medida por quão mais rápido o negócio consegue mudar. Se a sua decomposição está tornando a entrega mais lenta, a arquitetura está errada, por mais limpos que os diagramas pareçam.
Planejando uma decomposição ou migração para microsserviços?
Se você quer ajuda para desenhar um roadmap de decomposição realista, identificar bounded contexts e evitar os erros mais caros, vamos conversar.