Arquitetura de aplicações web sem complexidade desnecessária
Arquitetura não deve prever todos os futuros possíveis. Ela deve tornar as mudanças prováveis seguras e economicamente sustentáveis.
A pergunta não é “monolito ou microserviços?” Em um workshop de arquitetura, a equipe apresenta duas opções: A discussão rapidamente se torna ideológica. Uma parte associa monolito a software legado. Outra associa microserviços a escala e modernidade. Pouco se fala sobre: tamanho da equipe; frequência de deploy; limites do domínio; volume; consistência; suporte; monitoramento; orçamento; experiência operacional. A arquitetura correta não pode ser escolhida pelo prestígio do padrão. Ela precisa responder a forças concretas. Arquitetura é um conjunto de decisões caras de alterar Entre essas decisões: onde regras são executadas; como módulos se comunicam; quem possui os dados; como alterações são publicadas; como falhas são isoladas; como o sistema é observado; como consistência é mantida; onde escalar; quem pode alterar cada parte. Não é necessário definir todos os detalhes antecipadamente. É necessário identificar as decisões que restringem o futuro e registrar por que foram tomadas. Workshop: comece pelas forças Preencha a tabela antes de discutir tecnologia. Dimensão Perguntas Produto O que o sistema precisa fazer agora? Mudança Quais áreas mudam com maior frequência? Escala O que cresce: usuários, dados, processamento ou equipe? Risco Quais falhas possuem maior impacto? Consistência Quais operações precisam ser atômicas? Disponibilidade Quais partes podem ficar indisponíveis separadamente? Equipe Quantas equipes trabalham de forma independente? Operação Há observabilidade, CI/CD e plantão? Dados Quem é responsável por cada conjunto? Integrações Quais dependências externas existem? Custo Qual complexidade a organização consegue sustentar? A arquitetura deve acompanhar o problema atual e as mudanças mais prováveis, não um crescimento abstrato. Começar simples não significa misturar tudo Um monolito modular pode possuir: uma aplicação implantável; um banco principal; módulos explícitos; dependências controladas; interfaces internas; regras separadas; testes por módulo. Estrutura hipotética: Um módulo não deve importar tabelas, classes internas e detalhes de outro indiscriminadamente. Fluxo desejável: O monolito modular reduz custo operacional sem abandonar limites conceituais. Monolito não é sinônimo de aplicação mal organizada Um monolito pode: escalar horizontalmente; usar cache; executar jobs; utilizar fila; possuir CDN; ser implantado automaticamente; atender grande volume; separar módulos; oferecer alta disponibilidade. O problema aparece quando ele se torna um “grande bloco acoplado”: qualquer módulo acessa qualquer tabela; regras estão espalhadas; mudanças possuem efeitos imprevisíveis; build e testes são excessivos; deploy exige coordenação constante; nenhuma equipe possui limites claros. Microserviços não corrigem automaticamente esse desenho. Podem apenas transformar chamadas internas desorganizadas em chamadas de rede desorganizadas. O custo que aparece ao distribuir Ao separar um processo em serviços, surgem problemas novos: descoberta de serviço; autenticação entre serviços; autorização; TLS; timeouts; retries; idempotência; circuit breaking; filas; observabilidade distribuída; correlação; compatibilidade de contratos; deploy de várias versões; consistência eventual; dados duplicados; recuperação parcial. A documentação de microserviços da Microsoft destaca benefícios como implantação e escala independentes, mas também desafios relacionados a complexidade, comunicação, dados e operação. A pergunta é: O problema que a separação resolve vale todos esses custos? Sinais legítimos para separar um serviço Escala muito diferente Processamento de vídeo consome recursos muito diferentes do cadastro de usuários. Separação pode permitir escala específica. Isolamento de falha Uma geração pesada de relatório não deve derrubar o fluxo transacional. Tecnologia especializada Busca, mídia, machine learning ou telefonia podem exigir runtime e infraestrutura próprios. Limite de segurança Dados ou operações sensíveis podem exigir isolamento, acesso e auditoria específicos. Equipe independente Uma área possui responsabilidade, ritmo e capacidade operacional próprios. Ciclo de deploy incompatível Um módulo precisa ser publicado várias vezes ao dia, enquanto outro possui mudanças raras e controladas. Domínio estável A fronteira está bem compreendida. Separar um domínio ainda nebuloso cria contratos errados e difíceis de mover. Sinais fracos “Um dia teremos milhões de usuários.” “Grandes empresas usam.” “É mais moderno.” “Cada tabela será um serviço.” “Assim cada desenvolvedor usa sua linguagem preferida.” “Kubernetes resolve.” “Precisamos colocar microservices no currículo.” Esses argumentos não demonstram benefício para o sistema. A equipe precisa possuir a capacidade operacional antes O pilar de excelência operacional do Google Cloud inclui automação, observabilidade, preparação e melhoria contínua como parte da operação eficiente. Antes de aumentar a distribuição, avalie: builds reproduzíveis; testes confiáveis; deploy automatizado; rollback; logs estruturados; métricas; tracing; alertas; gestão de segredos; inventário; resposta a incidentes; propriedade clara. Sem essa base, cada serviço adiciona mais um ponto que pode falhar sem explicação. O artigo sobre DevOps e entrega confiável aprofunda essa capacidade. Banco compartilhado: atalho que elimina independência Dois serviços com o mesmo banco e acesso irrestrito às mesmas tabelas continuam fortemente acoplados. Problemas: um serviço altera schema do outro; regras ficam fora da API; deploy precisa ser coordenado; propriedade não está definida; consultas cruzadas mantêm dependência. Separação mais forte: Isso introduz consistência distribuída e duplicação controlada. Não é automaticamente melhor. É o custo da autonomia real. Quando consistência atômica entre áreas é frequente, talvez a fronteira esteja errada ou a separação seja prematura. Não distribua uma transação sem necessidade No monolito: Em serviços separados: Uma falha parcial exige: compensação; repetição; idempotência; estado intermediário; reconciliação; observabilidade. Padrões como saga podem ajudar, mas adicionam complexidade de negócio. Antes de distribuir, pergunte: a autonomia vale a consistência eventual; o negócio aceita estados intermediários; a equipe consegue reconciliar; o usuário entende o estado; há suporte para falhas parciais. Processamento assíncrono é uma ferramenta intermediária Nem toda necessidade exige um novo serviço. Uma aplicação pode mover tarefas pesadas para jobs: Usos: e mails; relatórios; importações; processamento de mídia; integrações; notificações. Isso reduz tempo de resposta e isola trabalho, mas exige: idempotência; retries; dead letter; monitoramento; status; tratamento de duplicidade. Um worker ainda pode fazer parte do mesmo produto e repositório. Cache resolve leitura, não desenho ruim Cache pode reduzir: consultas; processamento; latência; carga externa. Ele também cria: invalidação; dados desatualizados; chaves; concorrência; observabilidade; consumo de memória. Não use cache para esconder uma consulta incorreta sem compreender a causa. Pergunte: o dado pode ficar desatualizado; por quanto tempo; quem invalida; o que acontece em falha; existe fonte de verdade; o ganho foi medido. Frontend separado não exige microserviços Uma aplicação pode possuir: O artigo sobre frontend, backend e API explica essas responsabilidades. Uma API única pode servir web, aplicativo e integrações. Separar o frontend da implantação backend é uma decisão diferente de decompor o backend em serviços. Evite o “microfrontend por reflexo” Microfrontends podem permitir implantação independente por equipes, mas trazem: duplicação de dependências; inconsistência visual; roteamento; autenticação; comunicação; performance; observabilidade; coordenação de design system. Uma equipe pequena com um produto único costuma ganhar mais com módulos e componentes compartilhados. Registre decisões com ADR ADR significa Architecture Decision Record . Modelo: O objetivo não é burocracia. É impedir que decisões sejam reabertas sem contexto ou mantidas depois que as condições mudaram. Matriz de decisão Critério Monolito modular Serviços separados Equipe única Favorável Custo adicional Domínio instável Mais fácil de refatorar Contratos prematuros Deploy independente Limitado Forte Transação local Simples Difícil Escala seletiva Limitada Forte Isolamento de falha Parcial Potencialmente maior Operação Menor Maior Observabilidade Mais simples Distribuída Tecnologia diversa Limitada Possível Autonomia de equipes Moderada Forte Não some pontos mecanicamente. Um único requisito crítico pode dominar a decisão. Evolução gradual A AWS Prescriptive Guidance apresenta padrões como decomposição por capacidade de negócio, subdomínio, transação, strangler fig e branch by abstraction. Uma evolução possível: Fase 1 Monolito modular. Fase 2 Fila e worker para tarefa pesada. Fase 3 Interface explícita entre módulos. Fase 4 Dados e dependências isolados. Fase 5 Extração do módulo quando o benefício estiver comprovado. A abordagem strangler permite substituir partes gradualmente sem reescrever tudo de uma vez. Perspectiva “monolith first” O artigo Monolith First, de Martin Fowler, apresenta uma perspectiva cautelosa: iniciar com monolito e extrair serviços nas bordas pode ser mais seguro quando a equipe ainda não conhece bem os limites. O próprio texto reconhece que a evidência é baseada em relatos e deve ser tratada com cautela, não como lei universal. Existem projetos que justificam microserviços desde o início: muitas equipes experientes; domínio conhecido; escala específica; isolamento regulatório; plataforma distribuída por natureza; requisitos fortes de implantação independente. A decisão precisa mostrar essas forças. Checklist antes de criar um serviço [ ] O limite de negócio está claro. [ ] Existe uma equipe proprietária. [ ] O serviço pode possuir seus dados. [ ] O deploy independente gera benefício. [ ] A escala independente é necessária. [ ] Falhas parciais foram modeladas. [ ] Timeouts e retries estão definidos. [ ] Idempotência foi avaliada. [ ] Observabilidade distribuída existe. [ ] Contratos são testados. [ ] Segurança entre serviços foi planejada. [ ] O custo operacional foi aceito. [ ] Há estratégia de rollback. [ ] A extração é incremental. Se várias respostas forem “não”, mantenha o módulo interno por enquanto. Arquitetura para MVP Na primeira entrega, a prioridade costuma ser aprender e alterar rapidamente. O artigo sobre MVP sem atalhos perigosos recomenda reduzir escopo sem eliminar operação e segurança. Uma base comum pode ser: Ela não é adequada a todos os casos, mas oferece menos componentes para operar enquanto o domínio muda. O teste da arquitetura Uma boa arquitetura deve facilitar: mudança; diagnóstico; deploy; recuperação; segurança; teste; compreensão. Teste com perguntas: 1. Uma pessoa nova identifica onde a regra está? 2. Uma alteração local exige conhecer o sistema inteiro? 3. Uma falha pode ser localizada? 4. É possível publicar com segurança? 5. Existe rollback? 6. Dados possuem proprietário? 7. Dependências são explícitas? 8. O custo operacional cabe na equipe? O artigo sobre testes orientados por risco ajuda a validar fronteiras e contratos. A regra mais importante Não adicione um componente arquitetural porque ele pode ser útil. Adicione quando o problema que ele resolve for mais caro do que a complexidade que introduz. Arquitetura simples não significa ausência de engenharia. Exige modularidade, testes, observabilidade, segurança e disciplina. A diferença é que cada nova camada precisa justificar sua existência.