Nos últimos anos, tenho participado de diversos projetos de modernização de sistemas legados e transformação digital, onde a arquitetura de microsserviços foi adotada como solução para problemas de escalabilidade e evolução de software. Boa parte dessas iniciativas nasceu do desejo de modernizar aplicações legadas, levando organizações a abraçarem microsserviços em cloud como símbolo de inovação tecnológica. Em cada um desses projetos, ao realizar uma análise criteriosa da distribuição dos serviços, identifico um padrão preocupante que se repete com consistência. Uma segmentação excessiva que resulta em serviços ocos, desprovidos de qualquer regra ou valor de negócio. Às vezes são componentes que existem apenas para realizar consultas simples a bancos de dados, executar validações triviais ou repassar chamadas para outros serviços. O que deveria ser uma arquitetura distribuída coesa transforma-se em uma teia complexa de dependências, onde cada nó adiciona latência, pontos de falha e custos operacionais sem entregar o valor prometido de arquitetura de microsserviços.
Este fenômeno não é acidental. Isso acontece por uma compreensão superficial de princípios que fundamentam a arquitetura distribuída, combinada com a aplicação mecânica de padrões sem considerar o contexto e trade-offs inerentes. Isso deixa claro que a fragilidade observada não decorre da arquitetura escolhida, mas da falta de repertório técnico, de compreensão do negócio, muitas vezes agravadas por estruturas organizacionais que não favorecem decisões arquiteturais coerentes. Microsserviços não são uma evolução natural do design de software, mas uma escolha arquitetural que deve ser justificada por necessidades reais, bem compreendidas e fundamentadas.
Outra coisa que não era incomum ouvir sobre alguns sistemas era a mesma indagação, repetida em vozes diferentes: “nossa, isso aqui não é um microserviço, isto é um monólito”. Essa murmuração soa um pouco estranha, provavelmente vinda de alguém que nunca trabalhou em projetos de monólito raiz.
Diante disso, deixo algumas provocações: qual o “tamanho” que define um monólito? Quantos módulos definem um monólito? Quantos endpoints fazem um monólito? Vamos conversar um pouco sobre isso.
Posso falar de alguns projetos em que trabalhei que eram, de fato, monólitos. O primeiro deles era um ERP para recepção, avaliação, acompanhamento e contratação de projetos de P&D. Em resumo, todos os módulos geravam um único arquivo .war, que era utilizado para realizar o deploy em um Tomcat.
Uma curiosidade da época é que existia um papel dedicado exclusivamente às entregas, chamado Configuration Manager (CM). Esse profissional era responsável por todo o controle de versionamento de artefatos e documentos, além de gerenciar mudanças e auditorias. Não existia uma esteira automatizada para esse processo. O CM seguia alguns ritos de validação, auditava o que estava sendo entregue e gerava uma série de documentos antes de realizar o deploy da aplicação.
E, como todo bom monólito, o projeto possuía diversos módulos: pagamento, orçamento, cadastro, contábil, documentos, jornada de aprovação, configuração, perfilamento, entre outros. Esses módulos não eram projetos separados, tampouco possuíam repositórios distintos. Cada módulo era grande e complexo, com muitas regras de negócio e detalhes de perfilamento, autorização, etc. Tudo isso era organizado dentro do mesmo projeto, no mesmo repositório, que ao final gerava um único e grande war.
Outro projeto em que trabalhei, também um monólito, era um e-commerce. Todos os módulos geravam um único arquivo .war. Não existiam vários projetos ou múltiplas bibliotecas separadas. Naquela época, ainda não se falava em trabalhar com multi-modules usando Maven ou Gradle. A aplicação era dividida em módulos lógicos de busca, produtos, gestão de usuários, pedidos, estoque, pagamento, além de um módulo responsável por gerenciar e configurar a jornada, entre outros.
Nesse contexto, boas práticas de código eram extremamente importantes, algo que, inclusive, não deveria mudar muito hoje. Um bom código sempre será um bom código e reflete o repertório do desenvolvedor para perceber e resolver problemas. Implementar um design que permitisse uma modularização adequada, com contextos bem separados e organizados, chamadas entre módulos claramente definidas por contratos, além da aplicação de princípios como KISS, YAGNI, SOLID e GRASP, bem como a análise de acoplamento, referências e complexidade ciclomática, já eram práticas valiosas para projetos grandes e complexos como esse.
No caso do e-commerce, já estávamos aplicando o Strangler Pattern para realizar a quebra gradual do monólito em microsserviços. As decisões sobre como transformar o sistema e quais módulos seriam extraídos para microsserviços eram baseadas em métricas. A progressão desses movimentos era coordenada com a evolução do produto, sempre alinhada ao negócio. Nada era feito com base em suposições ou evoluções arbitrárias.
Em resumo, um monólito é o projeto. Portanto, meu primeiro ponto é que não é porque um serviço possui muitas funcionalidades ou regras de negócio densas e complexas que ele necessariamente representa um monólito. Se existe um contexto bem definido e uma responsabilidade clara de negócio com valor, isso não o torna um monólito. Alguns microsserviços podem ser grandes e densos em regras de negócio, assim como outros podem ser pequenos. Tudo depende do problema que se propõem a resolver.
“A arquitetura de um sistema de software tem pouca relação com a maneira como ele é implantado. A arquitetura diz respeito aos limites e às dependências. Você pode ter uma boa arquitetura em um monólito ou uma arquitetura terrível distribuída em microsserviços.” — Robert C. Martin
Martin Fowler e outros ícones da área já recomendaram algo conhecido como “monolith first”. Eu poderia montar uma lista enorme de justificativas, mas vou simplificar alguns pontos de forma mais direta. Não há problemas de rede entre chamadas de módulos, portanto não existe a necessidade de tratamentos de resiliência. A latência é praticamente nula, limitada basicamente ao context switch do processador. Não há custos associados a múltiplas infraestruturas ou pipelines. O monitoramento e o troubleshooting são mais simples e diretos. Também não há a necessidade de coordenação de entregas entre ambientes, entre outros.
Quando o monólito se torna um problema?
Durante muito tempo, a escalabilidade vertical tinha um teto máximo. A capacidade de adicionar mais recursos numa máquina era limitada fisicamente. Então surgiu a ideia de virtualizar hardware para ampliar o poder de processamento, memória e disco dos servidores, permitindo suportar o crescimento dos softwares que se tornavam cada vez maiores e mais complexos. Com mecanismos de distribuição de cargas e sistemas stateless, monólitos tranquilamente podem escalar horizontalmente também. Então onde realmente entra o problema dos monólitos ou, melhor, o que microsserviços realmente resolvem?
À medida que o sistema cresce, mais pessoas são necessárias para compor os times responsáveis pela manutenção e evolução. Qualquer tipo de alteração passa a exigir uma janela de manutenção. Aplicações grandes e pesadas tendem a ter startups lentos. Lembra do CM (Configuration Manager)? Ele também acabava se tornando um gargalo. Com muitos times entregando simultaneamente, era necessário versionar, auditar e documentar tudo o que entrava nas branches. O CM realizava os merges, gerava o artefato e, somente depois, fazia o delivery programado no servidor.
Assim, os problemas começavam a se manifestar em alguns pontos críticos. Primeiro, havia muitos desenvolvedores diferentes alterando o mesmo codebase, o que aumentava significativamente as chances de problemas, como conflitos durante o merge, especialmente porque esses merges eram extremamente dolorosos. Existia um grande esforço, tanto por parte do Configuration Manager quanto dos próprios desenvolvedores, para garantir que a integração de branches diferentes não removesse funcionalidades já entregues por outras frentes de trabalho e ferramentas como CVS e SVN eram, na época, o que havia de melhor disponível para o gerenciamento de repositórios.
O segundo problema era que toda e qualquer entrega exigia a interrupção completa da aplicação para a realização do deploy. Isso significava que, caso fosse identificado ou reportado algum bug crítico ao meio-dia, que demandasse um bug fix urgente, toda a aplicação precisava ser parada para que a correção fosse aplicada.
Na época, já começavam a surgir algumas experiências para garantir rollout deploy com zero downtime, geralmente por meio da duplicação de ambientes e do redirecionamento das chamadas. No entanto, para aquele contexto, esse tipo de abordagem era muito caro, pois exigia manter outra máquina equivalente para subir a aplicação e reconfigurar o gateway ou o balanceador.
É nesse contexto que o modelo de microsserviços surge com a proposta de resolver esses dois problemas. A partir disso, começam a emergir outros potenciais do modelo, como a capacidade de escalar times de forma independente e realizar deploys independentes.
Com um bom design, um eventual problema não derruba toda a aplicação, e a escala pode ser feita de acordo com a necessidade. Não é preciso manter um grande número de instâncias para serviços que recebem poucas chamadas, sendo possível escalar apenas aqueles mais críticos, que lidam com alta volumetria.
Esse modelo, no entanto, não se sustenta apenas com mudanças técnicas. Ele exige também novas formas de gestão e organização dos times. Estruturas mais alinhadas a domínios de negócio, times pequenos e autônomos, responsáveis de ponta a ponta pelo ciclo de vida do serviço (you build it, you run it), passam a ser modelos de referência de times maduros, capazes de entregar com consistência, qualidade e velocidade. Conceitos como Domain-Driven Design, bounded contexts e team ownership deixam de ser apenas boas práticas e se tornam pilares organizacionais.
Para apoiar esse modelo, surgem também novas técnicas e processos de gestão, como product teams orientados a valor, métricas focadas em resultados (outcomes) em vez de entregas (outputs), além de práticas como SLOs, SLIs e error budgets, que ajudam a equilibrar velocidade de entrega e confiabilidade dos sistemas.
“Grandes equipes de produto focam em resultados, não apenas em entregas. Não se trata de lançar funcionalidades, mas de gerar valor e impacto para os clientes.” — Marty Cagan, Inspired: How to Create Products Customers Love (2018)
Como consequência natural, aparecem ferramentas, modelos, processos e práticas para apoiar, acelerar e dar segurança à evolução de sistemas distribuídos. Entre eles estão containers, esteiras automatizadas de integração, os twelve-factor apps, práticas de DevOps, continuous delivery e continuous deployment, dentre outros.
A Lei de Conway e a estrutura organizacional
Um efeito importante que rege desde 1967 é a Lei de Conway, que basicamente diz que o modelo de estruturas de comunicação da organização influencia na arquitetura de software. Então surgem vários modelos de times, surgem também tentativas de arquiteturalmente projetar a inversão da Lei de Conway, mas sem sucesso. Ao longo do tempo, a lei vai surtindo efeito.
“Qualquer organização que projeta um sistema produzirá um projeto cuja estrutura é uma cópia da estrutura de comunicação da organização.” — Melvin Conway, 1967
Este é um ponto importante que muitas vezes passa despercebido e a equipe técnica acaba sendo penalizada, pois o benefício primário dos microsserviços não é técnico, é organizacional. O real valor está em permitir que múltiplos times trabalhem de forma independente, com autonomia para desenvolver, testar e implantar suas partes do sistema sem coordenação excessiva com outros times. Se a organização não está estruturada para isso, microsserviços provavelmente não são a resposta certa.
A experiência da Amazon com este modelo tornou-se lendária na indústria. No início dos anos 2000, Jeff Bezos emitiu um mandato que ficou conhecido como “API Mandate”. Todos os times deveriam expor suas funcionalidades através de interfaces de serviço. Toda comunicação entre times deveria acontecer através dessas interfaces. Não haveria exceções. Esta decisão forçou a Amazon a pensar em termos de contratos entre serviços, versionamento de APIs, e autonomia de times. O resultado foi uma organização capaz de escalar para milhares de desenvolvedores trabalhando em paralelo sem pisar nos pés uns dos outros.
Outra lição importante dessa história é que a Amazon não começou distribuindo tudo. A empresa evoluiu gradualmente de um monólito para serviços, identificando fronteiras naturais ao longo do caminho. Os serviços criados representavam capacidades de negócio significativas, como catálogo de produtos, processamento de pedidos, gerenciamento de inventário. Cada um com lógica de negócio substancial, cada um gerenciado por um time que compreendia profundamente aquele domínio.
Imagine a situação em que seu time depende de outro time para que seja criada uma nova API, mas esse time não tem disponibilidade para executá-la. Além disso, o ambiente em que você trabalha é tão rígido que, caso tentasse trazer o código para implementar por conta própria, seria necessário pedir acesso, tempo para entender como tudo funciona e aprovação do outro time e você não dispõe de todo esse tempo.
Qual a consequência disso? Geralmente, os times acabam implementando soluções por conta própria, devido à dificuldade de depender de outro time. Com isso, a arquitetura do projeto começa a gerar sistemas que fazem a mesma coisa, dobrando o custo operacional: há tempo gasto pelo time desenvolvendo, validando, homologando e entregando em produção. Além disso, o time passa a ter mais um projeto para manter ao longo do tempo, aumentando os custos com manutenção e infraestrutura.
Quando o refinamento de negócio é alinhado à arquitetura, o time consegue identificar necessidades nas fronteiras com outros times. Dessa forma, uma demanda pode ser antecipadamente solicitada e planejada para ser absorvida pelos outros times dentro dos ciclos de entrega. Esse nível de maturidade envolve simultaneamente tecnologia, arquitetura e produto, permitindo que equipes coordenem dependências de forma proativa, reduzam retrabalho e entreguem valor de forma consistente.
“Organizar os times ao redor do fluxo de mudanças e permitir que possuam seus próprios serviços reduz a carga cognitiva e melhora a velocidade e qualidade das entregas.” — Matthew Skelton & Manuel Pais, Team Topologies (2019)
Como reforça Skelton & Pais em Team Topologies, esse princípio explica como maturidade em tecnologia, arquitetura e produto permite que times antecipem necessidades e coordenem dependências de forma eficiente.
Quando fragmentamos o sistema sem compreender o modelo do domínio, criamos serviços que não expressam ideias coerentes, apenas operações desconexas. A estrutura organizacional pode até parecer ágil, mas o sistema resultante é rígido, difícil de evoluir, e custoso de operar.
“O modelo não é o diagrama. O modelo não é o código. O modelo é a ideia que o código e o diagrama expressam.” — Eric Evans
O problema real: design pobre, não tamanho
A confusão começa aqui. Esses monólitos não eram problemáticos por serem grandes ou por executarem em um único processo. Há um mantra que sempre reforço nesse tipo de discussão: código legado não significa código ruim; código ruim é código ruim, seja ele parte de um monólito ou distribuído em microsserviços.
O problema real surge da ausência de aplicação consistente de fundamentos e boas práticas de design, seja por falta de domínio técnico, incapacidade de negociar prioridades, pressões organizacionais mal administradas ou dívidas técnicas que deveriam ser temporárias. Muitas vezes, essas dívidas se tornam permanentes porque os profissionais não conseguem espaço para endereçá-las. Com o tempo, o problema se agrava e o conhecimento se perde, seja porque o código não é mais programado ativamente ou porque as mudanças na equipe fazem com que ninguém conheça totalmente a solução. Essa é a causa raiz, que se reflete tanto no código quanto na arquitetura.
“Um sistema bem projetado não é necessariamente um sistema distribuído. Um monólito bem projetado é infinitamente preferível a um conjunto de microsserviços mal projetados.” — Sam Newman
A distribuição do sistema não resolve problemas de design. Se o monólito sofre de acoplamento excessivo, responsabilidades mal definidas e limites confusos entre componentes, dividi-lo em microsserviços apenas distribuirá esses problemas pela rede. A complexidade adicional da computação distribuída amplifica os problemas existentes em vez de resolvê-los.
Já vi casos em que se introduz um serviço intermediário apenas para acessar um cache distribuído, como o Redis. Na prática, isso não faz sentido: o cache é um recurso que deveria ser acessado diretamente, próximo à aplicação. Ao adicionar uma camada extra, você acaba criando overhead desnecessário, aumentando latência, complexidade e pontos de falha, em vez de melhorar qualquer desempenho.
Além disso, surgem novos desafios operacionais, como monitoramento, logs, testes e manutenção do serviço intermediário. O esforço do time passa a ser gasto em gerenciar essa camada extra, enquanto o valor de negócio não é ampliado.
Essa decisão evidencia falta de repertório arquitetural, não se trata de limitação tecnológica, mas de competência técnica para identificar corretamente o problema antes de aplicar uma solução. Repertório não serve apenas para resolver problemas, mas para reconhecê-los e escolher a abordagem mais direta e eficiente.
Além disso, adicionar camadas desnecessárias gera custos extras de manutenção, aumenta o risco de falhas e degrada o desempenho, tornando a evolução do sistema mais lenta e sujeita a erros.
A ilusão da resiliência parcial
Um dos argumentos mais comuns em favor dos microsserviços é a resiliência. Se um microsserviço falha, apenas a funcionalidade afetada fica indisponível, não todo o sistema. Este argumento ignora que, em sistemas distribuídos, falhas parciais são muitas vezes mais problemáticas que falhas totais. Um monólito pode falhar completamente, mas ao menos o sistema fica em um estado conhecido: indisponível. Em um sistema de microsserviços mal projetado, falhas parciais propagam-se de maneiras imprevisíveis.
Vou ilustrar um caso que vivenciei em uma fintech que nasceu em ambiente moderno, com arquitetura de microsserviços desde sua concepção. Dezenas de serviços especializados foram criados. Cada funcionalidade do aplicativo mobile foi mapeada para microsserviços distintos com times de cartões, investimentos, transferências, pagamentos, histórico de transações. A promessa da arquitetura deveria ser clara: se um serviço falhar, os demais continuarão funcionando, garantindo disponibilidade parcial para os clientes.
Então vem a realidade. Quando o microsserviço de consulta de saldo apresentava instabilidade, todo o aplicativo tornava-se inutilizável. Os clientes não conseguiam visualizar cartões, área de investimentos não carregava, transferências ficaram bloqueadas. Até funcionalidades aparentemente independentes, como visualizar o histórico de transações, falhavam. O motivo estava no design arquitetural: cada tela do aplicativo, cada funcionalidade, fazia uma chamada ao serviço de saldo para exibir ou validar usando o saldo atual do cliente. Este microsserviço, considerado básico e simples, era na verdade um ponto único de falha disfarçado de serviço distribuído.
Do ponto de vista técnico de disponibilidade e resiliência do sistema, o resultado foi diferente do esperado. A promessa de que se um serviço cai, outras partes do sistema continuariam funcionando, nem sempre é uma verdade. O serviço de saldo transformou-se em um microsserviço do qual todos os outros dependiam direta ou indiretamente. Quando ele caía, o efeito dominó era imediato, assim como um monólito que cai e leva toda a operação junto. A diferença é que a arquitetura distribuída adicionou toda a complexidade de rede, latência, serialização, pontos de falha adicionais e custos operacionais, sem entregar o benefício prometido de resiliência parcial. A interdependência que derruba o sistema inteiro não é exclusiva de monólitos. Ela pode existir em microsserviços também, só que é mais complexa de tratar e identificar a causa raiz por estar distribuída, com problemas cascateando pela rede de forma menos óbvia.
Este padrão repete-se em inúmeras organizações. A Netflix documentou extensivamente suas batalhas contra dependências críticas em sua arquitetura de microsserviços. Mesmo sendo pioneira em práticas de resiliência, a empresa reconhece que certos serviços são tão centrais que sua falha compromete toda a plataforma. A diferença está na preparação. A Netflix investe pesadamente em circuit breakers, timeouts agressivos, fallbacks inteligentes e testes de caos que deliberadamente derrubam serviços em produção para validar a resiliência do sistema.
Quando se pensa em criar um novo microsserviço ou reescrever para um novo porque o antigo não é tão simples de dar manutenção, sempre digo: se você não tem repertório novo, vai repetir os mesmos erros, com risco de surtir o efeito contrário ao desejado, ou seja, do novo serviço já nascer “legado” (já mencionei que legado não é código ruim ou difícil de dar manutenção), porque o projeto ficou pior do que o que era considerado problemático. Já vi isso também. Vale lembrar que um microsserviço que faz chamada a outros tem complexidades e requisitos não funcionais que precisam ser garantidos além da reescrita dos requisitos funcionais, como mecanismos de defesa de resiliência, instrumentação de logs de sistema e de negócio, instrumentação de observabilidade, mecanismos de segurança, entre outros.
A Cloudflare aprendeu esta lição da forma mais dura em julho de 2019. Não foi um único problema grande que derrubou sua rede global. Foi uma combinação de falhas pequenas que se somaram de maneira catastrófica e todo o castelo de cartas caiu. Uma mudança de configuração no sistema de roteamento BGP, aparentemente inofensiva, interagiu mal com um bug no software de balanceamento de carga. Este problema, por si só, teria causado degradação leve. Mas coincidiu com alta carga em seu banco de dados PostgreSQL, que estava próximo de seus limites de capacidade. As consultas lentas ao banco de dados causaram timeouts em serviços que dependiam dele. Esses timeouts ativaram retries automáticos, que aumentaram ainda mais a carga no banco já sobrecarregado. O sistema de cache, que normalmente absorveria essa carga, estava sendo invalidado agressivamente devido a deploys recentes. Em minutos, a cascata estava completa. Não foi um ponto único de falha que derrubou a Cloudflare. Foi a ressonância entre múltiplas fragilidades do sistema, cada uma pequena, mas combinadas de forma letal.
“Seu sistema falhará. A questão não é se, mas quando, e quão graciosamente ele degradará.” — Michael Nygard
A resiliência não vem de evitar falhas individuais, vem de projetar sistemas que degradam graciosamente mesmo quando múltiplas coisas dão errado simultaneamente.
A realidade da rede: o inimigo invisível
Quando movemos de um monólito para microsserviços, introduzimos a rede como intermediária de todas as comunicações entre componentes. A rede, contudo, não é confiável. Esta verdade foi formalizada por Peter Deutsch e outros engenheiros da Sun Microsystems nas Falácias da Computação Distribuída. A primeira falácia é a crença de que a rede é confiável. A segunda é a suposição de que a latência é zero. Estas falácias são ilusões perigosas que arquitetos e desenvolvedores inexperientes muitas vezes assumem ou nem as consideram quando projetam sistemas distribuídos.
Cada chamada entre microsserviços atravessa a stack de rede completa, serialização dos dados, transmissão TCP/IP, roteamento através de load balancers, deserialização, processamento, serialização da resposta, e o caminho de volta. O que era uma invocação de método em memória, executada em nanossegundos, transforma-se em uma operação de rede que consome milissegundos. Quando um serviço depende de múltiplos outros serviços, essas latências acumulam-se. Uma requisição que exige chamadas sequenciais a cinco serviços, cada uma com 50ms de latência de rede, introduz 250ms apenas em overhead de comunicação, antes mesmo de considerar o processamento real.
O custo financeiro da rede em ambientes de cloud computing é com frequência subestimado. Provedores cobram por transferência de dados entre zonas de disponibilidade e entre regiões. Um sistema com centenas de microsserviços, fazendo milhões de requisições por dia entre si, pode acumular custos de rede que rivalizam com os custos de computação. Em um monólito, essas comunicações são invocações de método sem custo adicional.
“Quando você transforma uma invocação de método em uma chamada remota, não está apenas adicionando latência. Está adicionando um ponto de falha, um requisito de versionamento, um problema de serialização, e um item de custo operacional.” — Gregor Hohpe
Além da latência e do custo, a rede introduz modos de falha inexistentes em chamadas locais: pacotes podem ser perdidos, conexões interrompidas ou servidores ficarem inalcançáveis. Cada uma dessas situações exige tratamento explícito. Um sistema de microsserviços robusto precisa implementar retries com backoff exponencial, circuit breakers para evitar sobrecarga de serviços instáveis, timeouts agressivos para impedir bloqueio de threads e fallbacks para degradar funcionalidades de forma graciosa quando dependências falham. A ausência desses mecanismos, que vejo ser comum em muitos projetos, transforma uma arquitetura distribuída em um castelo de cartas, prestes a desmoronar ao menor sopro.
Qualquer sistema que se comunica com outro serviço pela rede, usando HTTP, TCP ou UDP, além de aumentar a latência pelo custo do round-trip (RTT), requer mecanismos de cache e pools de conexões, bem como tratamento e propagação de erros, sejam eles de negócio ou de infraestrutura. Além disso, é necessário implementar mecanismos de defesa para resiliência, como retries, circuit breakers e rate limits, especialmente em chamadas síncronas.
Parte desses mecanismos de defesa pode ser delegada à infraestrutura. O uso de service mesh com sidecars permite configurar, sem necessidade de implementação no código, aspectos como retries, timeouts, circuit breakers, rate limiting e observabilidade. Isso reduz o esforço de desenvolvimento e padroniza comportamentos de resiliência entre serviços, mas não elimina a necessidade de decisões arquiteturais conscientes.
Já na comunicação via mensageria, também existem mecanismos de defesa que precisam ser considerados para garantir tolerância a falhas. Nesse caso, os cuidados são diferentes, como idempotência de consumidores, reprocessamento seguro, dead-letter queues, controle de duplicidade, ordenação e gestão de acknowledgements ou offsets. Esses aspectos não são resolvidos por service mesh e exigem design explícito, seja no código, seja na configuração do broker.
Isso reforça que esses tratamentos não são opcionais. Ignorá-los resulta em sistemas frágeis e, quando essa fragilidade se propaga em cadeia, o cenário se assemelha a um jogo de Jenga com os sistemas, cada interação adiciona o risco de uma falha localizada escalar e derrubar partes maiores do sistema ou até a aplicação inteira. Algumas falhas são imediatas e simples de corrigir, outras só se manifestam em produção, durante incidentes com clientes.

Além dos custos técnicos e operacionais, essas falhas podem gerar inconsistências de dados, afetar a confiabilidade do negócio e causar perdas financeiras diretas, seja pela evasão de usuários, pela redução de uso do produto ou pelo impacto negativo na imagem da empresa. Em um cenário amplificado por redes sociais, uma percepção ruim do produto ou serviço pode se espalhar rapidamente, dificultando o crescimento e a recuperação da confiança do mercado.
“A primeira lei da arquitetura distribuída é: não distribua. A segunda lei é: se você não puder evitar a distribuição, minimize-a o máximo possível.” — Martin Fowler
O ponto não é defender que tudo deve ser monólito. Monólitos não são inerentemente bons ou ruins. O problema surge quando adotamos microsserviços e criamos serviços para resolver problemas técnicos em vez de problemas de negócio, gerando fragmentações excessivas e sem valor real. Um microsserviço que encapsula uma capacidade de negócio complexa, como gestão completa do ciclo de vida de pedidos, justifica sua existência independente. Um microsserviço que faz apenas validação isolada ou consultas ao banco de dados apenas para separar em um “microsserviço isolado” é fragmentação sem propósito, adicionando toda a complexidade da rede sem valor correspondente.
A fragmentação extrema: quando micro vira nano
A tentação de criar serviços cada vez menores tem levado muitas equipes a um extremo problemático. Microsserviços que são tão minúsculos, tão granulares, que se assemelham a funções serverless, mas sem os benefícios do modelo serverless. O termo microsserviços gera mal-entendido desde sua origem. A palavra micro sugere tamanho pequeno, levando equipes a buscar serviços cada vez menores, mas o conceito nunca foi sobre linhas de código ou tamanho do artefato de deploy. Microsserviços deveriam ser dimensionados pela responsabilidade de negócio que encapsulam, não por métricas técnicas de tamanho. Um microsserviço rico em lógica de negócio, que implementa completamente um contexto delimitado, pode ter dezenas de milhares de linhas de código e ainda assim ser apropriadamente dimensionado. O que chamamos de nanoserviços, serviços tão pequenos que fazem quase nada, são anti-pattern.
“O objetivo dos microsserviços não é ter muitos serviços pequenos. O objetivo é ter serviços que sejam independentemente implantáveis e que representem capacidades de negócio coerentes.” — Sam Newman
Esta fragmentação resulta em serviços que violam princípios de coesão. A granularidade é tão fina que o sistema perde qualquer senso de organização lógica. Esses microsserviços executam em containers ou máquinas virtuais dedicadas, consumindo recursos de infraestrutura para operações triviais que poderiam ser executadas em memória. Diferentemente de funções serverless, que escalam para zero quando não utilizadas e são cobradas apenas por execução, esses microsserviços mantêm uma infraestrutura rodando continuamente.
O resultado é uma arquitetura em que operações simples de negócio passam a exigir dezenas de chamadas de rede entre serviços. Cada chamada adiciona latência, cada serviço introduz complexidade operacional, e a aplicação se torna mais lenta e cara, sem ganhos proporcionais em flexibilidade ou escalabilidade.
Nesse cenário, a busca indiscriminada por microsserviços leva à criação de nanoserviços e de arquiteturas com serviços excessivamente “falantes” (chatty services), onde operações simples exigem múltiplas chamadas remotas. Essa fragmentação ultrapassa qualquer benefício razoável e se caracteriza claramente como um anti-pattern.
Microsserviços anêmicos: quando a segurança e o negócio evaporam
O conceito de modelo de domínio anêmico descreve objetos que contêm apenas dados, sem comportamento e encapsulamento fraco. Um objeto anêmico é essencialmente um saco de getters e setters, com toda a lógica de negócio residindo em classes de serviço separadas. Um microsserviço anêmico é aquele que expõe operações simplistas sobre entidades sem encapsular regras de negócio significativas. Tenho outro artigo sobre encapsulamento aqui. Considere um serviço de produtos que oferece endpoints para criar, ler, atualizar e deletar produtos, mas não possui qualquer lógica sobre precificação, categorização, relacionamento com fornecedores, ou gestão de estoque. Essas regras existem em outros serviços, ou pior, nos bffs ou na aplicação cliente que consome o serviço. O resultado é uma arquitetura onde o conhecimento de negócio está espalhado por múltiplos serviços e clientes, sem um lugar claro onde o modelo do domínio resida.
A lógica de encapsulamento em modelos de domínio não muda quando migramos de monólitos para microsserviços. Os mesmos princípios que nos guiam a não expor diretamente tabelas de banco de dados em uma aplicação monolítica devem guiar o design de APIs em microsserviços. Expor consultas sem critérios adequados de segurança, validação de negócio, e controle de acesso é uma falha arquitetural grave, independentemente da estratégia de deployment.
Um microsserviço que oferece um endpoint GET /users retornando todos os usuários do sistema sem paginação, sem filtros, sem verificação de autorização, viola princípios de design de arquitetura. Em um mundo regido pela LGPD e regulamentações similares ao redor do globo, expor dados pessoais sem controles apropriados não é apenas má arquitetura, é ilegalidade. Um serviço deve validar se o requisitante tem autorização para acessar aqueles dados específicos. Deve aplicar filtros baseados no contexto de segurança. Deve auditar o acesso. Deve respeitar preferências de privacidade e consentimento.
Isso é diferente de separar em serviços aplicando CQRS, por exemplo, que resolve um problema específico de volumetria separando o comando de mutação para um banco e uma query para outro banco, que inclusive pode ser um tipo de banco diferente baseado no problema. Nesse caso, há uma justificativa técnica clara baseada em requisitos de performance e escalabilidade de leitura versus escrita.
O conceito de Zero Trust é particularmente relevante em arquiteturas de microsserviços. Em um monólito, componentes internos muitas vezes confiam uns nos outros implicitamente, pois todos executam no mesmo processo e sob o mesmo contexto de segurança. Em microsserviços, cada serviço é um limite de segurança. Um serviço não deve confiar cegamente em requisições vindas de outros serviços. Deve validar tokens de autenticação, verificar autorizações, e aplicar políticas de segurança independentemente.
Um microsserviço verdadeiramente rico encapsula não apenas lógica de negócio, mas também políticas de segurança, validações, e regras de acesso. Um serviço de gestão de pedidos não apenas armazena pedidos. Ele verifica se o usuário tem permissão para criar aquele pedido. Valida se os produtos estão disponíveis. Aplica regras de precificação considerando promoções e descontos aplicáveis ao contexto do cliente. Verifica limites de crédito. Gera eventos para outros contextos. Audita a operação. Este é um serviço coeso, que encapsula completamente uma capacidade de negócio.
“Sem um modelo compartilhado, os desenvolvedores não falam a mesma língua, e o código torna-se um emaranhado de traduções e adaptações.” — Eric Evans
A prática comum de criar um microsserviço para cada tabela do banco de dados é uma manifestação extrema do modelo anêmico. Cada entidade recebe seu próprio serviço com operações CRUD. Quando uma operação de negócio requer coordenação entre múltiplas entidades, essa coordenação acontece através de chamadas entre serviços, orquestradas por algum componente externo. Este design quebra a coesão do modelo de domínio, distribui a lógica de negócio pela rede, expõe dados sem proteção adequada, e cria acoplamento temporal entre serviços que deveriam ser independentes.
Quando fragmentamos excessivamente, perdemos o modelo compartilhado. Cada microsserviço tem sua visão limitada de uma pequena parte do domínio, e ninguém possui uma compreensão holística do sistema. A arquitetura torna-se um labirinto onde até mesmo mudanças simples requerem alterações coordenadas em múltiplos serviços, e onde a implementação de uma política de segurança abrangente torna-se quase impossível.
A confusão irracional: distribuição não é modularização
“Fronteiras arquiteturais não são desenhadas em mapas de infraestrutura. São desenhadas em diagramas de dependências.” — Robert C. Martin
O erro mais caro que observo em projetos de microsserviços é a confusão entre distribuição e modularização. Modularização é a decomposição de um sistema em componentes com responsabilidades claras, baixo acoplamento, e alta coesão. Distribuição é a decisão de executar esses componentes em processos separados, possivelmente em máquinas diferentes. Estas são decisões ortogonais, mas muitas vezes são tratadas como sinônimas.

É perfeitamente possível, e muitas vezes desejável, ter um sistema altamente modularizado executando em um único processo. Linguagens e frameworks modernos oferecem mecanismos poderosos para modularização, como namespaces, packages, módulos, interfaces. Um monólito bem estruturado em Java pode usar packages e interfaces para definir fronteiras claras. Um sistema em .NET pode usar assemblies separados. Uma aplicação Node.js pode organizar-se em módulos coesos. Em todos esses casos, a modularização oferece os benefícios de manutenibilidade e evolução ordenada sem os custos da distribuição.
Quando equipes pulam direto para microsserviços sem primeiro dominar a modularização, os resultados são previsíveis: microsserviços que estão tão acoplados quanto o monólito que substituíram, mas agora com a complexidade adicional da rede. Dependências entre serviços proliferam. Mudanças em um serviço exigem mudanças coordenadas em outros. Deployments independentes tornam-se impossíveis porque os serviços não são verdadeiramente independentes.
O resultado é o que chamamos de monólito distribuído. Um sistema que mantém todas as desvantagens de um monólito tradicional, com acoplamento alto, necessidade de coordenação para mudanças, deployments que afetam múltiplos serviços, mas agora com todos os custos adicionais da distribuição, com latência de rede, pontos de falha distribuídos, complexidade operacional, necessidade de mecanismos de resiliência. É o pior dos dois mundos. Como discutimos anteriormente, um monólito é O projeto único. Quando distribuímos esse projeto sem quebrar adequadamente as dependências, criamos um monólito distribuído, onde os serviços estão tão interligados que qualquer mudança requer coordenação entre múltiplos times, exatamente o problema que microsserviços deveriam resolver.
Domain-Driven Design: a fundação necessária
Eric Evans desenvolveu Domain-Driven Design como uma abordagem para enfrentar a complexidade de software através do foco profundo no domínio de negócio e no modelo que o representa. Os conceitos de DDD, especialmente contexto delimitado e mapa de contexto, são absolutamente essenciais para projetar microsserviços que funcionem. Não são opcionais. São a base sobre a qual tudo mais se apoia.
“O modelo é o coração do design. Se o modelo é fraco, o código será fraco, não importa quão sofisticada seja a arquitetura.” — Eric Evans
Um contexto delimitado define os limites dentro dos quais um modelo particular faz sentido. Considere uma empresa de e-commerce. O conceito de produto significa coisas diferentes no contexto de catálogo, no contexto de estoque, e no contexto de precificação. No catálogo, um produto tem descrição, imagens, e categorias. No estoque, um produto tem quantidade disponível, localização no armazém, e ponto de reposição. Na precificação, um produto tem custo, margem, descontos aplicáveis, e estratégia de pricing dinâmica. Estes são três contextos delimitados diferentes, cada um com seu próprio modelo da entidade produto.
Estes contextos delimitados são os candidatos naturais a microsserviços. Quando projetamos microsserviços ao redor de contextos delimitados, cada serviço possui seu próprio modelo, seu próprio banco de dados, e sua própria lógica de negócio coesa. O serviço de catálogo não precisa saber sobre estoque. O serviço de estoque não precisa saber sobre precificação. A comunicação entre eles acontece através de interfaces bem definidas e, preferencialmente, de forma assíncrona através de eventos. Este design maximiza a independência dos serviços e minimiza o acoplamento.
Mapa de contexto, outra técnica estratégica central de DDD, descreve os relacionamentos entre contextos delimitados. Alguns contextos são upstream, fornecendo dados e funcionalidades para contextos downstream. Alguns relacionamentos são partnership, onde ambos os contextos evoluem juntos. Outros são conformistas, onde um contexto aceita o modelo do outro sem influência. Compreender esses relacionamentos é essencial para projetar as APIs entre microsserviços e para evitar acoplamento excessivo.
A linguagem ubíqua, conceito central do DDD, ganha ainda mais importância em arquiteturas de microsserviços. Quando times diferentes trabalham em serviços diferentes, a linguagem compartilhada sobre o domínio torna-se o fio que mantém a coerência do sistema. Se o time de catálogo chama algo de SKU e o time de estoque chama de código de produto, já temos o início de uma confusão que se propagará por toda a arquitetura.
Quando ignoramos DDD e criamos microsserviços ao redor de entidades técnicas ou operações CRUD, inevitavelmente criamos serviços que não representam capacidades de negócio coesas. O resultado são microsserviços anêmicos, altamente acoplados, que requerem coordenação constante para implementar qualquer funcionalidade de negócio real. Microsserviços com modelos fracos são microsserviços fadados a muitos problemas e ao fracasso.
A solução é começar com modularização rigorosa dentro do monólito. Identifique contextos delimitados genuínos usando técnicas de Domain-Driven Design. Organize o código ao redor desses contextos, com interfaces bem definidas entre eles. Garanta que módulos não acessem diretamente o banco de dados ou o estado interno de outros módulos. Quando essa modularização está sólida, quando os contextos delimitados são estáveis e genuinamente independentes, então, e somente então, considere extrair alguns deles como microsserviços separados, se houver justificativa real de negócio ou técnica para fazê-lo.
A jornada correta: do monólito aos microsserviços
Diante de todos os problemas discutidos, qual é a abordagem correta? A resposta não é evitar microsserviços completamente, mas adotá-los com maturidade e apenas quando genuinamente necessário. O conceito de Monolith First propõe começar com um monólito bem projetado e extrair microsserviços apenas quando houver motivos claros para fazê-lo.
Um monólito bem projetado é modular, com fronteiras claras entre componentes. É testável, com testes que podem executar rapidamente sem dependências externas. É implantável, com processos automatizados de build e deployment. Esse monólito serve como uma base sólida. À medida que o sistema cresce, certos módulos podem emergir como candidatos à extração: um módulo com requisitos de escala radicalmente diferentes dos demais, ou um módulo que se beneficiaria de ciclos de deployment independentes por mudar com frequência muito maior ou muito menor do que o restante do sistema.
Mas o que fazer quando o monólito existente não é bem projetado? Essa é a realidade da maioria das organizações. Nesse caso, iniciar por refatorações torna-se imperativo: reorganizar o sistema em módulos, mapear responsabilidades com base em métricas de uso, complexidade ciclomática e taxa de mudança. Identificar partes do sistema com alta volatilidade e isolá-las em módulos bem definidos prepara o terreno para extrações mais seguras e previsíveis.
Nesse ponto, o papel da arquitetura corporativa torna-se fundamental. A decisão de extrair microsserviços não é apenas técnica, mas também organizacional. A arquitetura corporativa atua como elemento de alinhamento entre domínio de negócio, estrutura dos times e decisões técnicas, garantindo que fronteiras de sistemas reflitam fronteiras reais de responsabilidade. Ela ajuda a evitar duplicações, define padrões mínimos de interoperabilidade, observabilidade e segurança, e apoia a distribuição de responsabilidades entre times de forma sustentável.
Quando extraímos um microsserviço, isso deve ser feito com cuidado. É fundamental garantir que ele represente um contexto delimitado genuíno. Desde o início, implementamos mecanismos de resiliência, como circuit breakers, retries e timeouts, além de estabelecemos observabilidade completa, com logging estruturado, métricas e tracing distribuído. Definimos SLAs claros e monitoramos continuamente seu cumprimento. A comunicação entre o monólito e o novo serviço deve ser cuidadosamente projetada, preferencialmente utilizando mensageria assíncrona para reduzir acoplamento temporal.
Essa abordagem incremental permite aprender com cada extração. Os erros de design cometidos no primeiro microsserviço não se propagam automaticamente para os demais. A organização desenvolve competências em sistemas distribuídos de forma progressiva, evitando um big bang arquitetural que distribui todo o sistema de uma vez e colapsa sob o peso da própria complexidade.
Componentes de resiliência: a infraestrutura essencial
Um sistema de microsserviços sem componentes adequados de resiliência é um desastre esperando para acontecer. A rede falhará. Serviços ficarão lentos. Bancos de dados ficarão sobrecarregados. Estas não são possibilidades remotas, são certezas.
Circuit breakers são o primeiro componente essencial. Um circuit breaker monitora chamadas a um serviço externo. Se uma porcentagem alta de chamadas falha ou excede um timeout, o circuit breaker abre, impedindo novas chamadas por um período. Isso evita sobrecarregar um serviço já instável e dá a ele tempo para se recuperar. Após o período de espera, o circuit breaker permite algumas chamadas de teste para verificar se o serviço recuperou-se. Este padrão simples previne cascatas de falhas que derrubam sistemas inteiros.
Timeouts agressivos são igualmente críticos. Uma chamada de rede sem timeout pode bloquear uma thread indefinidamente. Em sistemas com pool de threads limitado, algumas chamadas bloqueadas podem esgotar todas as threads disponíveis, tornando o serviço completamente irresponsivo. Cada chamada a outro serviço deve ter um timeout explícito, e esse timeout deve ser menor que o timeout do cliente que está esperando a resposta. A falta dessa disciplina em configuração de timeouts é uma causa comum de deadlocks distribuídos.
Bulkheads isolam recursos, garantindo que falhas em uma parte do sistema não consumam todos os recursos disponíveis. Se um serviço depende de dois serviços externos, pools de threads separados para cada dependência garantem que se uma dependência ficar lenta, ela não esgotará todas as threads e impedirá chamadas à outra dependência. Este isolamento de recursos é essencial para degradação graciosa.
Retries com backoff exponencial permitem recuperação de falhas transitórias de rede, mas devem ser usados com cuidado. Retries em cascata, onde um serviço retria uma chamada que internamente retria outra chamada, podem amplificar a carga em serviços downstream além do sustentável. Idempotência é crítica. Se uma operação será retriada, ela deve produzir o mesmo resultado quando executada múltiplas vezes. Implementar idempotência em operações de negócio complexas é não-trivial e repetidamente esquecido.
Microsserviços sem esses componentes de resiliência não degradam graciosamente. Eles colapsam espetacularmente, muitas vezes às três da manhã, quando você está dormindo.
O custo oculto: complexidade operacional
Além de todos os desafios técnicos discutidos, microsserviços introduzem complexidade operacional que é com frequência subestimada. Um monólito é um artefato a ser implantado. Microsserviços são dezenas ou centenas de artefatos, cada um com seu próprio ciclo de vida. Coordenar deployments, gerenciar versões, rastrear dependências entre serviços torna-se um trabalho em tempo integral.
“Distribuir um sistema multiplica o número de coisas que podem dar errado. E elas darão.” — Gregor Hohpe
Debugging em sistemas distribuídos é exponencialmente mais difícil. Quando uma requisição falha em um monólito, o stack trace revela exatamente onde e por quê. Em microsserviços, uma falha pode ter ocorrido em qualquer um dos serviços na cadeia de chamadas. Sem tracing distribuído, identificar qual serviço falhou, e por quê, pode levar horas ou dias. Mesmo com tracing, analisar traces que atravessam dezenas de serviços requer ferramentas e expertise especializadas.
Gerenciamento de dados torna-se exponencialmente mais complexo. Em um monólito, transações ACID garantem consistência. Um bloco transacional pode atualizar múltiplas tabelas com a garantia de que ou todas as mudanças serão commitadas, ou nenhuma será. Em microsserviços, onde cada serviço possui seu próprio banco de dados, consistência transacional entre serviços não existe. Para lidar com isso, existem padrões como Two-Phase Commit (2PC), Try-Confirm-Cancel (TCC), Event Sourcing e o padrão Saga. O mais comum é o Saga, onde uma operação de negócio é quebrada em múltiplas transações locais com compensações para desfazer mudanças em caso de falha. Implementar Sagas corretamente é difícil, e bugs em lógica de compensação podem deixar o sistema em estados inconsistentes difíceis de corrigir.
A complexidade operacional de microsserviços não é um detalhe de implementação. É um custo contínuo, pago todos os dias, por toda a vida do sistema.
Conclusão: a sabedoria da moderação
Não existe solução arquitetural universalmente correta. Microsserviços são uma ferramenta poderosa quando aplicados aos problemas certos, mas são um fardo pesado quando aplicados indiscriminadamente. A fragmentação excessiva que observo em tantos projetos não resulta de microsserviços em si, mas da aplicação mecânica de um padrão sem compreensão dos princípios que o fundamentam.
O ponto forte dos microsserviços não é técnico, é organizacional. Não é primariamente sobre escala, resiliência ou flexibilidade tecnológica. O real valor está em poder distribuir problemas complexos entre vários times que trabalham de forma independente. Para garantir um bom modelo de microsserviços, é importante que a organização monte times que reflitam a organização dos microsserviços desejada e, de preferência, siga os princípios de Domain-Driven Design para identificar fronteiras naturais.
Monólito não é um problema e tamanho de um serviço também não o torna necessariamente um monólito. O que define um monólito é se todo o projeto seja único, com todos os módulos em um único artefato de deploy. Um microsserviço grande e denso em regras de negócio não é um monólito, é um serviço bem dimensionado ao redor de um contexto delimitado.
A sabedoria está em começar simples. Um monólito bem projetado, com módulos claramente definidos ao redor de contextos delimitados do domínio, oferece a maioria dos benefícios atribuídos a microsserviços sem nenhum dos seus custos. À medida que o sistema e a organização amadurecem, microsserviços podem ser extraídos seletivamente, onde verdadeiramente agregam valor. Cada extração deve ser justificada, cada serviço deve representar uma capacidade de negócio coesa, cada interface entre serviços deve ser cuidadosamente projetada.
Domain-Driven Design não é opcional nesta jornada. É o mapa que nos guia para identificar fronteiras naturais onde a distribuição faz sentido. Componentes de resiliência não são refinamentos posteriores, são requisitos básicos desde o primeiro microsserviço. Observabilidade não é um nice-to-have, é a diferença entre um sistema que pode ser operado e um que colapsa sob mistério. A honestidade intelectual de reconhecer que nossos problemas atuais não justificam microsserviços requer repertório e coragem.
No final, a medida de sucesso não é quantos microsserviços temos, mas quão bem nosso software serve aos usuários e ao negócio. Um monólito simples que funciona perfeitamente supera infinitamente uma arquitetura de microsserviços que colapsa sob o peso de sua própria complexidade. A escolha sábia não é sempre a mais sofisticada. Muitas vezes, é a mais simples que ainda resolve o problema. A simplicidade é a sofisticação final.