Skip to main content

Command Palette

Search for a command to run...

Erros Clássicos de Arquitetura de Software que Ainda Vemos por Aí

Updated
5 min read

Arquitetura de software. Uma disciplina de 60 anos que ainda aparenta emergente, tamanha a sua volatilidade e a quantidade de revoluções sofridas nas últimas décadas. Nas últimas duas décadas, isoladamente, foi possível ver dezenas de novos modelos de arquitetura surgindo e sendo destruídos pelo tempo. As que permanecem, continuamente, são testadas por bruscas mudanças nas tecnologias ou por como nós interagimos com elas.

E com tantas revoluções se tornou inevitável que diversos equívocos se alastrassem e se tornassem quase uma regra entre os desenvolvedores e arquitetos menos experientes. Aqui nem vale entrar em como sempre se tornam calorosas discussões sobre organização de pastas, afinal, arquitetura de software não é sobre isso.

Arquitetura de software é irredutível, é a estrutura fundamental que sustenta um sistema. É o elemento essencial que sobra depois que todas as estratégias de simplificação e otimização se esgotam. Ela está sim presente em todas as camadas de nossa aplicação, e modelos como C4 se fundamentam justamente nessa ótica.

Em meio a tantas arquiteturas erradas, existem pelo menos 3 ideias que são comumente as principais causadoras das maiores dores que eu já encontrei:

1. Monolitos distribuidos

É muito provável que você já esteja lidando com essa, assustadora é sua popularidade. Desenvolvedores otimizam para uma escala inexistente, e no meio do processo, vendo o tamanho da complexidade que agregaram a um sistemas que antes era simples, decidem tomar atalhos terminando de enterrar uma ideia que já nasceu com data para morrer.

Em outros momentos o mesmo erro pode aparecer em decorrência de uma falha em definir corretamente os limites da aplicação, e em definir como os times trabalham nela. Muitas vezes uma decomposição equivocada por camadas técnicas, não pode domínios de negócios pode ser a motivadora.

As características são fáceis de se detectar:

  • Acoplamento temporal: Mudanças em um serviço exigem atualizações sincronizadas em vários outros.

  • Esquemas de dados compartilhados: Múltiplos serviços acessando diretamente as mesmas tabelas de banco de dados.

  • Cascatas de falhas: Um único serviço com problemas derruba todo o sistema.

  • Deployment acoplado: Impossibilidade de lançar serviços independentemente.

  • APIs anêmicas: Interfaces entre serviços que expõem detalhes de implementação internos em vez de comportamentos de domínio significativos.

A abordagem para evitar este problema começa com um entendimento profundo de Domain-Driven Design e o conceito de bounded contexts. Antes de qualquer decomposição física:

  • Identifique os domínios e subdomínios: Mapeie o espaço do problema em áreas coesas de funcionalidade de negócio.

  • Defina bounded contexts claros: Estabeleça fronteiras explícitas onde termos, regras e dados têm significados específicos e consistentes.

  • Modele os context maps: Documente como os diferentes contextos se relacionam e quais padrões de integração são apropriados para cada relacionamento.

  • Implemente por domínio, não por camada: A decomposição deve seguir linhas de negócio, não divisões técnicas.

2. Negligenciar as Dimensões Evolutivas da Arquitetura

Arquiteturas de software devem ser projetadas para evoluir em múltiplas dimensões simultaneamente. No entanto, muitas organizações ainda otimizam exclusivamente para velocidade de entrega inicial, ignorando outras características arquiteturais críticas.

Este erro se manifesta através de sistemas que inicialmente avançam rapidamente, mas rapidamente atingem um platô de produtividade. A velocidade de entrega desacelera dramaticamente à medida que o sistema cresce, até que a manutenção consome a maior parte dos recursos.

  • Segurança: Considerada tardiamente como uma camada a ser adicionada, não como um aspecto intrínseco do design.

  • Testabilidade: Sistemas sem pontos de injeção para testes ou com arquiteturas que tornam os testes automatizados impraticáveis.

  • Elasticidade: Incapacidade de escalar componentes específicos independentemente.

  • Observabilidade: Falta de instrumentação adequada para entender o comportamento do sistema em produção.

  • Manutenibilidade: Negligência com práticas que facilitam mudanças futuras.

Para evitar este problema, devemos:

  • Definir explicitamente as características arquiteturais importantes: Além de funcionalidade, quais aspectos não-funcionais são críticos para o sucesso?

  • Implementar fitness functions: Medidas objetivas, preferencialmente automatizadas, que verificam se a arquitetura mantém suas características desejadas ao evoluir.

  • Incorporar validação arquitetural no pipeline: Os testes de fitness functions devem fazer parte do CI/CD, não verificações manuais ocasionais.

  • Balancear trade-offs conscientemente: Documentar e comunicar explicitamente as decisões de priorização entre diferentes características.

3. Big Design Up-Front sem Mecanismos de Feedback

Decisões arquiteturais são essencialmente apostas sobre um futuro incerto. Apesar disso, persiste a tendência de criar arquiteturas completas e detalhadas antes de qualquer implementação, sem estratégias para validar essas decisões incrementalmente.

Este padrão, reminiscente do desenvolvimento waterfall, manifesta-se em equipes que produzem extensos documentos de arquitetura, diagramas elaborados e modelos teóricos, mas carecem de mecanismos para testar empiricamente suas hipóteses arquiteturais.

  • Resistência à mudança: Quanto mais detalhado o design inicial, maior o investimento psicológico e mais difícil admitir a necessidade de ajustes.

  • Arquitetura baseada em suposições: Decisões fundamentadas em previsões de carga, padrões de uso ou requisitos de negócio que podem nunca se materializar.

  • Otimização prematura: Complexidade adicionada para resolver problemas que nunca ocorrerão na escala real do sistema.

  • Paralisia arquitetural: Times relutantes em prosseguir sem resolver cada detalhe antecipadamente.

O caminho mais efetivo é:

  • Tratar decisões arquiteturais como hipóteses: Documentar explicitamente as suposições por trás de cada decisão significativa.

  • Implementar mecanismos de feedback rápido: Utilizar protótipos, MVPs, spike solutions e testes A/B para validar ideias arquiteturais.

  • Adotar o conceito de último momento responsável (Last Responsible Moment): Postergar decisões irreversíveis até que se tenha informação suficiente.

  • Incorporar arquitetura evolutiva: Projetar para mudança, não apenas para requisitos atuais conhecidos.

  • Utilizar Architecture Decision Records (ADRs): Documentar não apenas a decisão, mas o contexto, alternativas consideradas e critérios de avaliação.

A arquitetura de software eficaz não é sobre perfeição teórica, mas sobre criar sistemas que cumpram seus objetivos enquanto permanecem adaptáveis às mudanças inevitáveis em requisitos, tecnologia e escala.

À medida que a indústria continua a evoluir, talvez a habilidade mais importante que um arquiteto pode desenvolver seja a capacidade de aprender e adaptar-se continuamente, mantendo sistemas que façam o mesmo.

Como sua organização lida com estes desafios? Você reconhece algum destes padrões em seus sistemas atuais? Compartilhe suas experiências e abordagens para evitar estes erros clássicos.