Trilha 4 · Escala e evolução · ~18 min

Fui chamado para "ajudar a resolver os problemas dos microserviços" de uma empresa de 25 pessoas. Eles tinham 40 microserviços. Quarenta. Para 25 pessoas. Cada mudança de feature tocava 4 ou 5 serviços, exigia coordenar deploys, e um bug de produção virava uma caçada por logs em cinco lugares diferentes. Eles tinham comprado toda a complexidade operacional dos microserviços (rede, deploy distribuído, consistência eventual, observabilidade distribuída) e nenhum dos benefícios (não tinham times autônomos o suficiente para justificar, nem escala que exigisse). Tinham feito microserviços porque "é assim que empresa séria faz".

A migração que eu recomendei foi na direção contrária: consolidar aqueles 40 serviços de volta num monolito modular. E é aí que mora a lição desta aula, e o motivo de "voltar de microserviços para monolitos" ser um movimento cada vez mais comum na indústria: microserviço é uma decisão organizacional e operacional, não técnica. Você não migra por elegância; migra quando a estrutura do seu time e da sua escala exigem, e paga um preço alto por isso.

O que microserviços realmente compram

Microserviços dão a você uma coisa principal: autonomia de deploy por time. Cada time pode deployar seu serviço sem coordenar com os outros, escolher sua própria stack, escalar sua parte independentemente. Isso é valiosíssimo — quando você tem muitos times que se atrapalham deployando o mesmo monolito. Amazon, Netflix, Uber têm centenas de times; sem microserviços, eles literalmente não conseguiriam deployar (imagine 500 devs coordenando um deploy do mesmo binário).

Mas repare no que está entre os "quando": muitos times. O benefício central de microserviços é organizacional. Se você tem um time, ou três, você não tem o problema que microserviços resolvem — e vai pagar o custo mesmo assim.

O que microserviços não compram: eles não deixam seu código mais limpo (um monolito modular bem feito é igualmente limpo — aulas DDD estratégico + Arquitetura Hexagonal no FakeFlix e Comunicação entre módulos: o padrão Public API Provider). Não deixam mais fácil de entender (fica mais difícil — agora a lógica atravessa a rede). Não são "mais escaláveis" por natureza (um monolito modular escala horizontalmente bem para a imensa maioria dos casos). Modularidade ≠ microserviços. Você pode ter fronteiras impecáveis num único deploy.

A Lei de Conway como bússola

Melvin Conway, 1967: "organizações projetam sistemas que espelham sua própria estrutura de comunicação." A leitura moderna (o "Inverse Conway Maneuver"): desenhe as fronteiras dos seus serviços para casar com as fronteiras dos seus times.

Isso dá o critério de extração mais confiável que existe. Você extrai um módulo para um serviço quando:

  • Um time dedicado é dono daquele domínio e é atrapalhado por compartilhar o pipeline de deploy com os outros. O acoplamento organizacional (times pisando no deploy uns dos outros) é a evidência — não uma preferência técnica.
  • Aquele domínio tem um perfil de escala genuinamente diferente (ex.: o video-processor do FakeFlix consome 20x mais CPU que o resto e precisa escalar sozinho — aula Subdomínios e comunicação assíncrona (BullMQ): mensagens vs. eventos).
  • Aquele domínio tem requisitos operacionais próprios (compliance isolado, ciclo de release diferente, stack especializada).

Se nenhuma dessas condições existe, extrair é comprar complexidade sem comprar benefício — o erro dos 40 serviços para 25 pessoas.

Bússola de bolso: microserviços são a resposta à pergunta "nossos times estão se atrapalhando para entregar de forma independente?". Se a resposta é não, a pergunta não é sua ainda.

O custo escondido da rede

Toda chamada que era uma função vira uma chamada de rede. Isso não é um detalhe — muda a natureza do sistema:

  • A rede falha. Uma chamada de função sempre retorna; uma chamada de rede pode dar timeout, perder pacote, chegar duplicada. Você agora precisa de retries, circuit breakers, timeouts, fallbacks — em cada fronteira que era um simples service.method().
  • A rede é lenta. Nanosegundos viram milissegundos. Uma operação que fazia 10 chamadas internas agora faz 10 saltos de rede — a latência empilha.
  • Consistência vira eventual. Uma transação que abrangia tudo num monolito não abrange mais nada entre serviços. Você troca transações ACID por sagas e compensações (aula Event-Driven na prática: Transactional Outbox, idempotência e sagas). Isso é uma escada inteira de complexidade nova.
  • Debug fica distribuído. O stack trace único vira uma investigação com tracing distribuído em cinco serviços. Sem observabilidade séria, um bug de produção vira arqueologia.

Nada disso é impeditivo — Netflix opera assim numa boa. Mas tudo isso é custo fixo que você paga por serviço, e que só vale quando o benefício organizacional supera. Para 25 pessoas, não supera.

O bom monolito modular já é a preparação

Aqui está a boa notícia que amarra o curso inteiro: se você seguiu as aulas DDD estratégico + Arquitetura Hexagonal no FakeFlix, Comunicação entre módulos: o padrão Public API Provider e Persistência e State Isolation, você não precisa escolher agora. Um monolito modular com fronteiras defendidas (fitness functions), comunicação via Public API e estado isolado é um conjunto de microserviços que só não foram separados fisicamente ainda. O ponto de corte é o contrato (aula Comunicação entre módulos: o padrão Public API Provider).

Extrair Billing, na prática, é:

// Antes: provider local, chamada de função na mesma memória
{ provide: BILLING_SUBSCRIPTION_STATUS_API, useClass: BillingSubscriptionStatusProvider }

// Depois: provider remoto, mesma interface, mesmo contrato
{ provide: BILLING_SUBSCRIPTION_STATUS_API, useClass: HttpBillingSubscriptionStatusProvider }

Content não muda uma linha (ele depende do contrato BillingSubscriptionStatusApi, não da implementação). O estado já estava isolado (Billing sempre foi o único a tocar suas tabelas), então não há "desemaranhar banco" — o pesadelo nº 1 de quem tenta extrair de um monolito espaguete. Você move o módulo, sobe um deploy, troca o binding. É por isso que a estratégia do curso é "comece monolito modular, extraia sob demanda": você colhe a simplicidade operacional do monolito agora e mantém a opção de microserviço para quando (e se) a evidência organizacional aparecer.

Isso também explica a "abordagem híbrida" que aparece na indústria: você não precisa ser 100% monolito ou 100% microserviços. Extraia os 2 ou 3 módulos que têm justificativa (o video-processor que escala diferente, o domínio de um time autônomo) e mantenha o resto junto. A arquitetura evolutiva não escolhe um lado — ela mantém as duas opções abertas e move a fronteira física só onde há evidência.

Antes → Depois (o raciocínio de decisão)

Pergunta Se NÃO Se SIM
Temos múltiplos times se atrapalhando no mesmo deploy? Fique no monolito modular Candidato a extração
Algum domínio escala radicalmente diferente? Fique junto Extraia esse domínio
Temos observabilidade/ops distribuída maduras? Extrair vai doer Pode extrair com segurança
O estado já está isolado por módulo? Resolva isso ANTES (aula Persistência e State Isolation) Extração é barata

Tradeoffs

  • "Voltar" para monolito também tem custo. Consolidar 40 serviços não é grátis — é uma migração com risco. A lição não é "monolito sempre", é "escolha pela evidência organizacional, e reverta se errou". Tanto ir quanto voltar são decisões evolutivas.
  • Extrair cedo demais vs. tarde demais. Cedo demais = complexidade sem benefício (os 40 serviços). Tarde demais = um monolito que, se não for modular, vira impossível de separar quando a hora chega. A defesa contra "tarde demais" não é extrair cedo — é manter o monolito modular (fronteiras, Public API, estado isolado). Assim a extração é sempre barata quando você precisar.
  • Nem toda escala precisa de microserviço. Muitas vezes o gargalo é um subdomínio (o video-processor), não o sistema. Escalar aquele worker (aula Subdomínios e comunicação assíncrona (BullMQ): mensagens vs. eventos) resolve sem fragmentar tudo. Microserviço para escala é frequentemente um exagero — escala horizontal do monolito + isolamento do subdomínio pesado cobre a maioria dos casos.

Cola mental

  • Microserviço resolve problema organizacional/de escala, não de código. Modularidade ≠ microserviços.
  • Benefício central: autonomia de deploy por time. Sem muitos times, você não tem o problema que ele resolve.
  • Lei de Conway: fronteiras de serviço devem casar com fronteiras de time. Extraia quando times/escala/ops exigem, não por estética.
  • Custo escondido: rede falha, é lenta, consistência vira eventual, debug vira distribuído — custo fixo por serviço.
  • Bom monolito modular já é a preparação: o contrato (aula Comunicação entre módulos: o padrão Public API Provider) é o ponto de corte; extrair = trocar a implementação do provider. Estado já isolado (aula Persistência e State Isolation) = extração barata.
  • Híbrido é legítimo: extraia os 2-3 que têm justificativa, mantenha o resto junto.

Referências para Aprofundamento

A decisão

Conway e a rede

Exercícios

Exercício 1 — Recomende ou desaconselhe

Para cada contexto, diga se você extrairia microserviços e por quê: (a) 6 devs, um time, produto de 1 ano, tudo cabe na cabeça, sem gargalo de escala; (b) 90 devs em 12 times, todos deployando o mesmo monolito e vivendo em conflito de merge/deploy; (c) um monolito modular onde só o processamento de vídeo (1 subdomínio) satura CPU nos picos.

Ver resposta

✓ Resposta: (a) Não extrairia. Um time, cabe na cabeça, sem gargalo: você não tem o problema organizacional nem o de escala que microserviços resolvem; extrair só compraria complexidade operacional. Fique no monolito modular. (b) Extrairia (candidato forte). 90 devs em 12 times em conflito de deploy é exatamente a dor que microserviços resolvem — autonomia de deploy por time (Lei de Conway). Desenhe fronteiras de serviço que casem com os times. (c) Extração parcial (híbrida). Não fragmente tudo; extraia (ou pelo menos escale independentemente) só o subdomínio de vídeo, que tem perfil de escala distinto. Muitas vezes basta rodar mais workers daquele subdomínio (aula Subdomínios e comunicação assíncrona (BullMQ): mensagens vs. eventos) sem separar em serviço — comece pelo mais barato que resolve.

Exercício 2 — Ache a evidência certa

Um tech lead quer extrair o módulo de pagamentos "porque microserviço é mais escalável". Que pergunta você faz para descobrir se há evidência organizacional real, e que resposta justificaria a extração?

Ver resposta

✓ Resposta: A pergunta certa não é sobre escala técnica, é organizacional: "existe um time dedicado a pagamentos que está sendo atrapalhado por compartilhar o pipeline de deploy/release com os outros — e que ganharia deployando de forma independente?". (Complementos: pagamentos tem requisito de compliance/isolamento próprio? Tem perfil de escala radicalmente diferente?) Uma resposta que justificaria: "sim, temos um time de pagamentos que precisa fazer releases de compliance em cadência própria e hoje fica bloqueado esperando a janela de deploy do monolito". Isso é evidência organizacional. "Microserviço é mais escalável" não é evidência — é dogma (aula Por que não existe arquitetura perfeita); um monolito modular escala horizontalmente para quase todos os casos.

Exercício 3 — O ponto de corte

Explique por que, num monolito que segue o padrão Public API Provider (aula Comunicação entre módulos: o padrão Public API Provider) e State Isolation (aula Persistência e State Isolation), extrair um módulo não exige tocar no código dos módulos consumidores. Onde exatamente entra a mudança?

Ver resposta

✓ Resposta: Porque os consumidores dependem do contrato (a interface pública, ex.: BillingSubscriptionStatusApi), não da implementação concreta nem do interno do módulo. A mudança da extração fica confinada a uma nova implementação do mesmo contrato: troca-se o provider local (BillingSubscriptionStatusProvider, chamada de função) por um provider remoto (HttpBillingSubscriptionStatusProvider, chamada HTTP/gRPC), alterando só o binding no módulo. O consumidor continua injetando e chamando a mesma interface. Além disso, como o estado já estava isolado (State Isolation — ninguém lia a tabela de Billing direto), não há banco para desemaranhar. A mudança entra na fronteira (implementação do provider + infra de deploy), não no miolo dos outros módulos.

Exercício 4 — Diagnóstico dos 40 serviços

Volte à história de abertura. Liste dois custos que aquela empresa de 25 pessoas estava pagando e dois benefícios de microserviços que ela não estava colhendo. Conclua por que consolidar fazia sentido.

Ver resposta

✓ Resposta: Custos que pagavam (dois): (1) complexidade operacional distribuída — 40 deploys, rede entre serviços, consistência eventual, tracing distribuído para 25 pessoas manterem; (2) fricção de desenvolvimento — uma feature tocava 4-5 serviços e exigia coordenar deploys, e um bug virava caçada de logs em cinco lugares. Benefícios que não colhiam (dois): (1) autonomia de deploy por time — com 25 pessoas não havia times numerosos o bastante se atrapalhando para justificar; (2) escala independente relevante — não tinham a escala que exige separar fisicamente os domínios. Conclusão: pagavam todo o custo de microserviços e recebiam ~nenhum benefício, o pior dos mundos. Consolidar num monolito modular (mantendo as fronteiras via Public API) devolvia a simplicidade operacional sem perder a modularidade — e preservava a opção de reextrair os poucos serviços que um dia tivessem justificativa real.

Exercício 5 — Desafio: o veredito

Uma empresa quer extrair o módulo de busca do monolito para um serviço próprio. Os dados:

  • 14 devs em dois times; a busca é mantida por 3 pessoas de um deles;
  • a busca responde 60% das requisições do site e precisa de 8× mais CPU que o resto;
  • o deploy do monolito leva 25 minutos e sai duas vezes por semana; a busca precisaria de ajustes de relevância quase diários;
  • a busca lê o catálogo (tabela de Content) direto, com dois JOINs;
  • não há tracing distribuído, e o log é texto solto sem correlationId.

Dê o veredito — extrair, não extrair, ou extrair depois — e justifique nos termos da aula.

Ver resposta

✓ Resposta: Extrair depois — e a ordem importa mais do que o veredito. Os argumentos a favor são reais e dos bons: cadência de deploy divergente (diária contra duas vezes por semana, com 25 minutos de build no caminho) é bloqueio concreto entre times, exatamente o que microserviço compra; e perfil de recurso desproporcional (60% do tráfego, 8× de CPU) é o caso de escala individual que justifica artefato separado — hoje se escala o monolito inteiro para atender a busca. Há também um time identificável de 3 pessoas, o que satisfaz Conway. Mas dois pré-requisitos não estão cumpridos. (1) A busca lê a tabela de Content direto, com JOINs. Não existe fronteira por onde cortar: extrair agora produziria um serviço que precisa do banco do monolito, ou seja, um monolito distribuído — mesmo acoplamento, agora com latência de rede e falha parcial no meio, e ainda por cima com o JOIN virando N chamadas HTTP. Antes de extrair, é preciso uma Public API em Content e a busca consumindo dado próprio (provavelmente um índice alimentado por evento). (2) Não há tracing distribuído nem correlationId no log. No dia em que a busca falhar, ninguém vai conseguir seguir a requisição atravessando dois processos — o custo escondido da rede cobra primeiro na depuração. Observabilidade não é enfeite que se adiciona depois: é a condição para operar o que se distribuiu. Sequência recomendada: primeiro fechar a fronteira lógica (Public API, fim dos JOINs cruzados, índice próprio) e instrumentar o log com correlationId; medir de novo. Se a dor de deploy e de escala persistir — e ela provavelmente vai, porque as causas são estruturais —, a extração passa a ser barata, porque o corte já existe. Repare que quase todo o trabalho útil acontece antes da extração, e boa parte dele melhora o sistema mesmo que a extração nunca aconteça: é o argumento da aula de que o bom monolito modular já é a preparação.