#arquitetura-de-software#trade-offs#aprendizagem

Pensamento arquitetural: aprender a decidir, não apenas implementar

Uma reflexão sobre como o pensamento arquitetural transforma a visão de desenvolvimento: trade-offs, características de qualidade, ADRs e verificação contínua.

Durante o desenvolvimento de software, é comum concentrarmos nossos esforços em compreender um problema e encontrar uma maneira de resolvê-lo. Investigamos tecnologias, estudamos ferramentas, organizamos o código e buscamos implementar funcionalidades que atendam aos requisitos estabelecidos.

Entretanto, conforme comecei a aprofundar meus estudos em arquitetura de software e a desenvolver meus projetos pessoais, percebi que fazer uma funcionalidade funcionar representa apenas parte do desafio.

Uma implementação pode atender perfeitamente ao seu objetivo imediato e, ainda assim, introduzir dependências desnecessárias, dificultar futuras alterações ou comprometer características importantes da aplicação.

No capítulo 2 de Fundamentos da Arquitetura de Software, Mark Richards e Neal Ford apresentam um conceito que contribuiu para essa mudança de perspectiva: o pensamento arquitetural.

Uma de suas ideias centrais é a necessidade de ampliar o olhar sobre as decisões técnicas, considerando não apenas a parte do sistema que estamos modificando, mas também as consequências dessa mudança para a aplicação como um todo.

Essa reflexão transformou gradualmente a maneira como passei a analisar meus projetos. Em vez de perguntar apenas "como posso implementar essa funcionalidade?", comecei a acrescentar outras questões:

Quais responsabilidades estão envolvidas? Que dependências essa implementação introduzirá? Quais partes do sistema poderão ser afetadas? E como essa decisão influenciará futuras modificações?

Essas perguntas não tornam o desenvolvimento necessariamente mais complexo. Elas nos ajudam a compreender melhor o problema antes de escolher uma solução.

Da implementação à visão sistêmica

Imagine uma aplicação cuja finalidade seja comparar dois documentos XML e apresentar suas diferenças. À primeira vista, o requisito parece simples: receber dois arquivos, identificar divergências e exibir os resultados.

Podemos começar a implementação diretamente pela interface, criando componentes responsáveis por receber os documentos, interpretar seus conteúdos e realizar a comparação. Essa abordagem pode funcionar.

Entretanto, quando observamos a aplicação de maneira mais ampla, começamos a identificar responsabilidades distintas:

  • A interpretação do XML precisa conhecer como os resultados serão apresentados?
  • O mecanismo de comparação deveria depender diretamente dos componentes visuais?
  • Se modificarmos a interface, também será necessário alterar as regras de comparação?

Essas perguntas ajudam a identificar relações que talvez não fossem percebidas durante uma implementação orientada apenas ao resultado imediato. Separar a lógica de comparação da apresentação pode facilitar sua manutenção, permitir testes mais isolados e reduzir os efeitos de mudanças na interface.

Mas essa separação também precisa ser justificada. Criar diversas camadas, interfaces e mecanismos de extensão para uma funcionalidade pequena pode introduzir mais complexidade do que o problema exige.

Foi nesse ponto que comecei a compreender uma distinção importante: pensar arquiteturalmente não significa utilizar o maior número possível de padrões ou abstrações, mas entender as consequências das estruturas que escolhemos construir.

O objetivo não é eliminar todas as dependências, mas reconhecer quais são necessárias, quais podem representar riscos e quais precisam ser controladas.

Essa perspectiva também modificou minha compreensão sobre organização de código. Distribuir arquivos em diretórios chamados domain, application ou infrastructure não garante, por si só, uma arquitetura desacoplada. O que realmente importa é como as responsabilidades estão definidas, como os componentes colaboram e quais mudanças podem se propagar entre eles. A estrutura de pastas pode expressar uma intenção arquitetural, mas são as relações entre os componentes que demonstram como essa intenção foi implementada.

Toda decisão envolve consequências

Quando começamos a analisar as alternativas disponíveis para resolver um problema, percebemos que dificilmente encontraremos uma solução que maximize todas as qualidades desejáveis sem introduzir custos:

  • Uma abstração pode reduzir o acoplamento, mas aumentar a quantidade de componentes que precisam ser mantidos.
  • Uma estratégia de processamento paralelo pode melhorar o desempenho em determinadas condições, mas também introduzir desafios de concorrência e maior complexidade.
  • Uma arquitetura distribuída pode permitir que diferentes serviços sejam escalados independentemente, mas exigir mecanismos adicionais de comunicação, observabilidade e tratamento de falhas.

Essas compensações são conhecidas como trade-offs. O conceito é importante porque muda a maneira como avaliamos as decisões técnicas. Em vez de perguntar qual solução é absolutamente melhor, precisamos investigar qual alternativa atende melhor aos requisitos existentes e quais custos estamos dispostos a aceitar.

Nos meus estudos, essa reflexão também ajudou a questionar uma tendência comum durante o aprendizado: acreditar que uma arquitetura mais sofisticada é necessariamente superior. Ao conhecer padrões, princípios e diferentes estilos arquiteturais, é natural querer utilizá-los. Entretanto, a existência de uma técnica não significa que todo problema justifique sua aplicação.

Uma solução simples pode ser suficiente para determinado contexto. Em outro, a mesma simplicidade pode concentrar responsabilidades e tornar a evolução mais difícil.

Richards e Ford exploram essa realidade ao apresentar a ideia da least worst architecture, ou arquitetura menos inadequada. A expressão reforça que decisões arquiteturais frequentemente envolvem compromissos, e não a descoberta de uma solução perfeita. O que torna uma escolha adequada é sua relação com as necessidades, restrições e características do sistema. Isso exige desenvolver a capacidade de justificar decisões, reconhecendo não apenas seus benefícios, mas também suas limitações.

O que realmente precisamos preservar?

Outro aspecto importante do pensamento arquitetural é compreender que o funcionamento correto de uma aplicação não representa, isoladamente, toda a sua qualidade. Um sistema pode atender aos requisitos funcionais e ainda apresentar problemas de segurança, dificuldades de manutenção ou desempenho inadequado.

Essas preocupações nos levam às características arquiteturais, abordadas por Richards e Ford nos capítulos 4 e 5 de Fundamentos da Arquitetura de Software. Entre elas estão manutenibilidade, segurança, desempenho, escalabilidade e testabilidade.

Cada aplicação apresenta necessidades diferentes, e nem todas essas características precisam receber a mesma prioridade. Imagine uma ferramenta que processa documentos contendo informações sensíveis. Nesse contexto, evitar exposições indevidas pode representar uma restrição mais importante do que oferecer mecanismos sofisticados de escalabilidade. Já uma aplicação responsável por processar grandes volumes de informações simultaneamente pode possuir requisitos de desempenho e capacidade operacional mais rigorosos.

Compreender essas diferenças ajuda a evitar decisões baseadas exclusivamente em preferências tecnológicas. Também nos permite perceber que características arquiteturais precisam influenciar escolhas concretas:

  • Se a manutenibilidade é importante, precisamos considerar como mudanças previsíveis afetarão os componentes.
  • Se a segurança é uma prioridade, precisamos identificar ameaças e estabelecer controles adequados.
  • Se a testabilidade é relevante, devemos observar se a estrutura permite verificar comportamentos sem depender desnecessariamente de sistemas externos.

Foi a partir dessas reflexões que comecei a enxergar a arquitetura como algo mais próximo de um conjunto de decisões justificáveis do que de uma estrutura fixa a ser reproduzida. Não basta afirmar que queremos um sistema seguro, manutenível ou testável. Precisamos compreender quais decisões favorecem essas características e como poderemos avaliar seus resultados.

Decidir também significa preservar o raciocínio

Durante o desenvolvimento de um projeto, determinadas escolhas parecem evidentes porque conhecemos o problema que estamos tentando resolver. Entretanto, esse contexto nem sempre permanece claro.

Com o passar do tempo, podemos esquecer por que escolhemos determinada abordagem, quais alternativas consideramos ou quais limitações aceitamos. Uma separação entre componentes que inicialmente possuía uma justificativa importante pode parecer desnecessária para alguém que desconheça o contexto original. O contrário também pode acontecer: uma solução simples, escolhida conscientemente para atender aos requisitos iniciais, pode ser interpretada como resultado de falta de planejamento.

O código demonstra como algo foi implementado, mas nem sempre consegue explicar completamente por que aquela abordagem foi escolhida. É nesse contexto que entram os Architecture Decision Records (ADRs).

Esses registros ajudam a preservar decisões arquiteturais significativas, documentando o problema, as alternativas consideradas, a justificativa da escolha e suas consequências. A intenção não é registrar cada detalhe da implementação. Decisões pequenas e localizadas podem ser documentadas diretamente no código, em testes ou em outros materiais técnicos, quando necessário. Os ADRs são especialmente úteis quando uma escolha estabelece restrições importantes ou influencia a estrutura e a evolução do sistema.

Outro aprendizado relevante é que esses registros não precisam representar decisões permanentes. Uma abordagem pode ser adequada hoje e precisar ser revisada quando os requisitos mudarem. Preservar o histórico permite compreender por que a arquitetura evoluiu de determinada maneira.

Mas documentar uma decisão não significa comprovar que ela é adequada. Dependendo de sua importância, também precisamos avaliar riscos, cenários e trade-offs. Métodos como o Architecture Tradeoff Analysis Method (ATAM) ajudam a estruturar essa investigação, relacionando decisões arquiteturais aos atributos de qualidade que precisam ser atendidos. Embora uma avaliação formal não seja necessária para toda escolha técnica, o raciocínio por trás desses métodos é útil: compreender as prioridades, investigar alternativas e tornar explícitas as consequências esperadas.

Da decisão à verificação contínua

Até aqui, podemos reconhecer três atividades importantes: compreender o problema, avaliar alternativas e registrar decisões relevantes. Entretanto, existe uma questão que passou a ganhar importância durante meus estudos e a evolução dos projetos pessoais:

Como saber se as decisões continuam sendo respeitadas depois que o sistema começa a mudar?

Imagine que determinada aplicação tenha estabelecido uma restrição arquitetural: o módulo responsável pelas regras de domínio não deve depender diretamente de bibliotecas de interface. Essa decisão pode estar documentada e corretamente implementada. Mas, durante o desenvolvimento de novas funcionalidades, alguém pode introduzir uma dependência que contrarie essa restrição.

A aplicação talvez continue funcionando normalmente. Os usuários podem não perceber nenhuma diferença imediata. Ainda assim, uma propriedade considerada importante para a arquitetura terá sido violada.

Esse exemplo demonstra por que uma decisão não encerra necessariamente seu ciclo quando é implementada. Precisamos de mecanismos que ajudem a verificar se determinadas expectativas continuam sendo atendidas.

No capítulo 6 de Fundamentos da Arquitetura de Software, Richards e Ford abordam as Architecture Fitness Functions, ou funções de aptidão arquitetural. Esses mecanismos permitem avaliar propriedades relevantes por meio de verificações objetivas, que podem envolver testes automatizados, análise estática, métricas ou monitoramento. No exemplo anterior, uma verificação de dependências poderia identificar importações proibidas entre módulos.

Da mesma forma, testes automatizados podem verificar comportamentos funcionais importantes durante a evolução de uma aplicação. Em ferramentas web, testes End-to-End (E2E), realizados com soluções como o Playwright, permitem reproduzir fluxos de utilização e verificar se diferentes partes do sistema continuam funcionando em conjunto.

Entretanto, é importante distinguir essas responsabilidades. Um teste E2E que verifica se o usuário consegue selecionar arquivos, executar uma comparação e exportar resultados fornece evidências sobre esse fluxo funcional. Ele não demonstra automaticamente que a aplicação possui baixo acoplamento ou que todas as restrições arquiteturais estão sendo respeitadas. Por isso, testes funcionais e verificações arquiteturais precisam atuar de maneira complementar.

A ideia central não é automatizar todas as decisões nem presumir que uma suíte de testes aprovada garante a qualidade da arquitetura. É construir mecanismos capazes de oferecer evidências relevantes sobre aquilo que precisamos preservar.

Uma decisão arquitetural não termina necessariamente quando é implementada. Suas consequências precisam continuar sendo observadas à medida que o sistema evolui.

Arquitetura como processo contínuo de aprendizagem e decisão

Ao relacionar os conceitos apresentados neste artigo, percebo que estudar arquitetura de software não consiste apenas em aprender padrões, estilos arquiteturais ou formas de organizar componentes. Envolve desenvolver uma maneira diferente de investigar problemas:

  • Antes de implementar, precisamos compreender quais responsabilidades estão envolvidas e como a solução poderá afetar o restante do sistema.
  • Ao comparar alternativas, precisamos reconhecer que toda escolha apresenta benefícios, custos e limitações.
  • Ao tomar decisões relevantes, precisamos preservar suas justificativas para que possam ser compreendidas e reavaliadas.
  • E, durante a evolução do software, precisamos observar se as propriedades e comportamentos considerados importantes continuam sendo respeitados.

Isso não significa que todas as decisões exigem análises extensas, documentos formais ou mecanismos sofisticados de verificação. O esforço precisa ser proporcional à relevância e aos riscos de cada escolha. Também não significa que sempre conseguiremos antecipar todas as consequências. A arquitetura evolui porque os requisitos mudam, novas informações aparecem e limitações antes desconhecidas se tornam evidentes.

Talvez esse seja um dos aprendizados mais importantes que venho consolidando: uma boa decisão arquitetural não é aquela que nunca precisa ser modificada, mas aquela que possui justificativas compreensíveis, consequências avaliadas e condições para ser revisitada quando necessário.

Essa mudança de perspectiva tem influenciado a maneira como estudo e desenvolvo meus projetos pessoais. Em vez de utilizar princípios arquiteturais apenas como regras que devem ser seguidas, procuro compreender os problemas que eles buscam resolver, as situações em que fazem sentido e as consequências de sua aplicação. Ainda há muito a investigar, especialmente sobre como transformar princípios arquiteturais em práticas concretas e verificáveis. Mas compreender que arquitetura envolve um processo contínuo de decisão, avaliação e aprendizagem já representa uma mudança importante na maneira de enxergar o desenvolvimento de software.

Aprofundando os conceitos

Este artigo apresentou uma visão geral sobre como o pensamento arquitetural conecta decisões técnicas, trade-offs, características de qualidade, documentação e verificação contínua. Cada um desses assuntos possui fundamentos, técnicas e limitações que merecem uma investigação mais detalhada.

Por isso, na série Da decisão à evolução: fundamentos do pensamento arquitetural, vamos aprofundar progressivamente esses conceitos, relacionando a fundamentação teórica a exemplos práticos de desenvolvimento. Começaremos pela visão sistêmica, pelas dependências e pelo acoplamento entre componentes, procurando compreender como decisões aparentemente pequenas podem influenciar a evolução de uma aplicação.

A proposta é continuar explorando uma pergunta que tem orientado meus estudos:

Mais importante do que saber implementar uma solução é compreender por que escolhemos construí-la daquela maneira — e como avaliar se essa escolha continua fazendo sentido.


Referência bibliográfica

RICHARDS, Mark; FORD, Neal. Fundamentos da Arquitetura de Software: Uma Abordagem de Engenharia. O'Reilly/Alta Books.

Conceitos abordados: pensamento arquitetural, características arquiteturais, medição e governança arquitetural, trade-offs e decisões arquiteturais.