Novo: Eficify One em beta aberto. Crie seu primeiro ambiente sem cartão.Conhecer a plataforma →

Arquitetura evolutiva: pare de reescrever o sistema a cada ano

Estrutura modular interconectada crescendo organicamente como um organismo vivo, representando evolução de sistema
CompartilharSeguir

Todo sistema começa elegante. Depois de 18 meses, vira uma bola de lama que ninguém quer tocar. A culpa não é da equipe original. É da ausência de uma estratégia de evolução. Arquitetura evolutiva não é sobre antecipar o futuro. É sobre deixar o presente mais barato de mudar.

A armadilha da decisão irreversível

Engenheiros adoram perfeição. Querem projetar o sistema ideal, com microsserviços, event sourcing, CQRS e tudo mais. O problema é que toda decisão de arquitetura tem um custo de reversão. Microsserviços são baratos de deploy, mas são fáceis de desfazer. Um domínio mal cortado gera acoplamento entre serviços que nenhuma quantidade de Docker Compose vai resolver.

Decisões reversíveis são o fundamento da arquitetura evolutiva. Isso significa escolher padrões que te permitem mudar de ideia. Exemplo prático: em vez de decidir agora se o módulo de pagamentos será um microsserviço, coloque a lógica de pagamento em um pacote interno com interface clara. Se amanhã precisar extrair, o custo será menor. Se não precisar, você não pagou o preço da infraestrutura distribuída.

Decisões reversíveis não são decisões preguiçosas. São decisões com custo de mudança calculado.

Fronteiras de módulo: o corte que importa

A maioria das discussões sobre arquitetura termina em duas opções: monólito ou microsserviços. Essa é uma falsa dicotomia. A questão real é: como você corta os domínios dentro do seu sistema?

Bons cortes respeitam três princípios:

Acoplamento (contextual coupling): dois módulos mudam juntos porque pertencem ao mesmo domínio de negócio? Se sim, devem ficar próximos. Se mudam juntos por coincidência técnica (ex.: ambos usam PostgreSQL), esse não é um bom motivo para acoplá-los.

Coesão funcional: cada módulo resolve um problema bem definido. Se você não consegue descrever o módulo em uma frase curta, ele provavelmente faz coisas demais.

Estabilidade temporal: módulos estáveis (regras de negócio centrais) devem depender de módulos instáveis (interfaces de usuário). O inverso causa propagação de mudanças desnecessária.

src/
├── checkout/           # Contexto delimitado: pedido
│   ├── application/    # Casos de uso, orchestrators
│   ├── domain/         # Entidades, value objects, regras
│   └── infrastructure/ # Repositórios, adaptadores externos
├── catalog/            # Contexto delimitado: produtos
│   ├── application/
│   ├── domain/
│   └── infrastructure/
└── shared/             # Apenas utilitários, sem domínio

Perceba que cada contexto tem as três camadas internas (application, domain, infrastructure). Isso não é sobre arquitetura em camadas clássica. É sobre garantir que a lógica de negócio nunca dependa diretamente de detalhes de implementação (banco de dados, APIs externas). Essa inversão de dependência é o que permite trocar o PostgreSQL por DynamoDB sem tocar no domínio.

Contratos: a única coisa que importa entre módulos

Quando dois módulos se comunicam, o que importa é o contrato entre eles, não a implementação interna. Em código, contratos são interfaces explícitas. Em sistemas distribuídos, contratos são contratos.

Contratos explícitos resolvem um problema específico: refatoração silenciosa. Quando você muda a assinatura de uma função ou a estrutura de um payload de API sem versionamento, está quebrando módulos que dependem de você. Contratos versionados, com schemas validados (Protobuf, JSON Schema, OpenAPI), capturam essa informação e forçam discussão antes da quebra.

Além disso, contratos orientados a domínio significam que você não passa objetos de domínio entre módulos. Você passa DTOs que representam o que aquele módulo precisa, não o que o outro módulo tem. Isso desacopla o modelo, impede vazamento de detalhes internos e torna refatorações seguras.

Quando extrair um serviço: a decisão que vale dinheiro

Aqui está a pergunta que todo tech lead enfrenta aos 18 meses de projeto. Microsserviços resolvem três problemas específicos:

Problema Monólito resolve? Microsserviço resolve? Alternativa?
Equipes pisando umas nas outras no mesmo codebase Parcialmente Sim Módulos com ownership por equipe
Escalabilidade independente de um componente Não Sim Auto-scaling do monólito (se empacotado corretamente)
Isolamento de falhas Não Sim Circuit breakers no monólito
Tecnologias diferentes por domínio Não Sim Plugins ou módulos polyglot

Se você tem apenas o primeiro problema, módulos com boundaries claros e ownership por equipe resolvem. Microsserviços são resposta desproporcional. Se você tem o segundo ou terceiro, aí sim vale pagar o preço da complexidade operacional.

A armadilha comum é extrair um serviço porque "está na moda" ou porque outra empresa fez isso. Cada serviço extraído adiciona latência de rede, complexidade de observabilidade, custo de infraestrutura e necessidade de gerenciamento de dados distribuídos (sagas, eventos, eventual consistency). Se o domínio não justifica esses custos, você criou um monólito distribuído, que é pior que ambos.

flowchart TD
    A{É necessário escalar o módulo independentemente?}
    A -->|Sim| B{Múltiplas equipes precisam trabalhar no mesmo módulo?}
    A -->|Não| C{Módulo tem domínio de negócio independente?}
    B -->|Sim| D[Extrair como serviço independente]
    B -->|Não| E[Manter no monólito]
    C -->|Sim| F{Dados próprios e isolados?}
    C -->|Não| E
    F -->|Sim| G{Tamanho menor que 30% do monólito?}
    F -->|Não| H[Considerar strangler fig pattern]
    G -->|Sim| I[Extrair via strangler fig]
    G -->|Não| J[Refatorar primeiro antes de extrair]
Árvore de decisão para extração de serviço

Use a árvore de decisão acima como filtro inicial. Se qualquer resposta for "não", o default é manter como módulo dentro do monólito. Somente prossiga se houver ganho concreto e mensurável.

Armadilhas que destroem arquiteturas evolutivas

Acoplamento por (infraestrutura): quando dois módulos compartilham o mesmo banco de dados, você tem acoplamento estrutural. Mesmo que sejam módulos separados no código, uma migration no banco compartilhado vai exigir coordenação entre equipes. Solução: cada módulo com seu próprio schema ou banco.

Vazamento de domínio: quando o módulo de checkout começa a importar entidades do módulo de catalog, você tem vazamento. A lógica de um domínio está sendo duplicada ou compartilhada de forma implícita. Solução: introduza um domínio compartilhado (ex.: SKU, que pertence a nenhum domínio específico) ou um serviço de domínio que ambos chamam.

Big bang de reescrita: começar um projeto do zero dizendo "da próxima vez vamos fazer certo" é receita para o mesmo resultado. Arquiteturas evolutivas crescem incrementalmente, com cada módulo sendo refatorado para satisfazer os critérios de boundary quando for tocado.

Falsa modularidade: diretórios separados não são módulos. Se você tem pastas mas toda alteração em uma exige mudanças em dez outras, não há fronteira de módulo. Use a métrica de impacto: quantos arquivos você precisa tocar para fazer uma alteração típica em cada módulo?

Critérios objetivos para sua próxima decisão

Antes de tomar qualquer decisão de arquitetura, responda a estas perguntas:

  1. Qual problema específico isso resolve? Se a resposta for genérica ("vamos escalar melhor"), refine até ter um problema concreto.
  2. Qual o custo de implementação (infraestrutura, operacionais, cognitivos)?
  3. Qual o custo de reverter se eu estiver errado?
  4. Posso fazer isso de forma incremental ou preciso fazer tudo de uma vez?
  5. Quem vai manter isso quando eu não estiver mais no projeto?

Se a primeira pergunta não tiver uma resposta específica, pause. Arquitetura existe para resolver problemas, não para antecipá-los. A melhor arquitetura é aquela que te permite mudar de ideia com o menor custo possível.

Conclusão

Arquitetura evolutiva não é sobre usar a tecnologia mais nova ou o padrão mais sofisticado. É sobre disciplina de boundaries, contratos explícitos e decisões reversíveis. O monólito bem modularizado de hoje é a base para os serviços certos de amanhã. O segredo é saber quando um módulo merece subir de status e quando merece ficar exatamente onde está. Essa disciplina é o que separa sistemas que crescem dos que precisam ser reescritos a cada release.


Se cada nova entrega está ficando mais cara e arriscada, e o sistema parece travar a cada release, vale uma conversa de 30 minutos. A gente avalia suas fronteiras de módulo e desenha um caminho de evolução sem a dor de reescrever tudo, sem compromisso.

Fale com um especialista da Eficify

CompartilharSeguir
Bruno Carrilhos

SOBRE O AUTOR

Bruno Carrilhos

CTO · Eficify

Executivo de tecnologia, cofundador da Eficify, com mais de 20 anos de experiência na criação, evolução e sustentação de soluções digitais. Atua nas áreas de desenvolvimento de software, dados, inteligência artificial, cloud computing, cibersegurança e operações de missão crítica. É bacharel em Ciência da Computação, com formação em Ciência de Dados e Inteligência Artificial e pós-graduação em Segurança da Informação.

VAMOS CONVERSAR

Clareza técnica antes da próxima decisão.

Conte seu cenário e receba uma leitura externa da sua arquitetura e dos próximos passos.

Agendar um assessment