Arquitetura de software permeia discussões técnicas diárias, mas raramente definimos com precisão seus fundamentos. Este artigo explora a distinção crucial entre padrões e estilos arquiteturais, examinando o MVC como padrão consolidado e a arquitetura Hexagonal como estilo flexível, além de orientar quando e como aplicá-los em contextos reais.
Antes de mergulhar nas especificidades, estabeleçamos o que constitui arquitetura de software.
Segundo a ISO/IEC/IEEE 42010:2022, arquitetura de software é:
“A estrutura fundamental de um sistema de software, que define seus componentes, suas relações e os princípios que orientam seu projeto e evolução.”
Em outras palavras, a arquitetura é a fundação organizacional sobre a qual todo o sistema é construído. Ela representa um conjunto de decisões críticas que definem a estrutura e o comportamento de longo prazo da aplicação.
Como Martin Fowler resume:
Arquitetura é sobre decisões importantes. Importantes o suficiente para serem difíceis de mudar — Martin Fowler
Eu, particularmente, gosto de enxergar arquitetura como design em alto nível, presente em diversas camadas do sistema. Grady Booch expressa algo semelhante:
Toda arquitetura é design, mas nem todo design é arquitetura
Arquitetura transcende implementação específica, trata-se de decisões estruturais que moldam a evolução e manutenibilidade do sistema ao longo do tempo.
“Se você acha que uma boa arquitetura é cara, tente uma arquitetura ruim” — Brian Foote and Joseph Yoder
Distinguindo Estilos de Padrões Arquiteturais
Estilos arquiteturais
Representam abstrações gerais que definem famílias de sistemas com características estruturais comuns. Estabelecem princípios organizacionais sem prescrever detalhes práticos. É uma forma de classificar soluções arquiteturais segundo uma estrutura e comportamento comuns.
Padrões arquiteturais
Assumem caráter mais prescritivo, oferecendo soluções estruturadas e replicáveis para problemas recorrentes no design de sistemas de software. Como o nome sugere, constituem fórmulas testadas de implementação.
Padrão MVC: Evolução e Contexto
Como boa parte da computação, nada é tão novo quanto parece. O padrão MVC surgiu em 1970, criado por Trygve Reenskaug na Xerox PARC enquanto programava com Smalltalk. O objetivo era organizar interfaces gráficas, separando responsabilidades que anteriormente se misturavam caoticamente no mesmo arquivo.
Nos anos 90 e 2000, o MVC foi amplamente adotado por frameworks como Ruby on Rails, Struts e Spring MVC, estabelecendo a tríade fundamental:
- Model: cuida dos dados e regras de negócio
- View: exibe os dados ao usuário
- Controller: interpreta ações do usuário e coordena a interação entre Model e View
A popularização gerou variações como MVP (Model View Presenter), MVVM (Model View ViewModel) e HMVC (Hierarchical MVC), cada uma ajustando responsabilidades para contextos específicos.
Integração com Arquitetura em Camadas
Em 2003, Martin Fowler no livro “Patterns of Enterprise Application Architecture” desenvolveu o padrão Passive View, influenciando diretamente o MVP. No mesmo trabalho, descreveu padrões como Layered Architecture, Domain Model, Service Layer e Data Mapper, que moldaram arquiteturas corporativas modernas.
Essa evolução levou à integração do MVC com arquitetura N-Tier, refinando a separação de responsabilidades e desacoplando regras de negócio da camada de apresentação. Facilitava a intercambialidade de frameworks focados em View, reduzindo o impacto de mudanças tecnológicas.
Surge assim o padrão que conhecemos hoje: Controller, Service e Repository. Nesse cenário, o padrão MVC passou a se concentrar exclusivamente na camada de apresentação, enquanto o domínio da aplicação era implementado segundo outras abordagens, muitas vezes influenciadas pelo DDD.
MVC no Ecossistema Moderno
O Spring MVC ilustra bem a evolução da arquitetura orientada a padrões. Com a introdução das anotações no Java, o framework tornou-se significativamente mais conciso e legível em comparação a outras soluções baseadas em XML ou configurações verbosas.
Em 2009, foi introduzida a anotação @ResponseBody
, que permitia que os controllers retornassem dados diretamente (em JSON, XML ou outro formato), eliminando a necessidade de renderizar HTML, um marco importante para a construção de APIs.
Já em 2014, a anotação @RestController
consolidou esse comportamento ao combinar @Controller
com @ResponseBody
, simplificando ainda mais a criação de endpoints RESTful.
Com a popularização de arquiteturas baseadas em microserviços, o estilo REST ganhou ampla adoção, principalmente por sua simplicidade, flexibilidade e baixa barreira tecnológica. REST tornou-se o padrão de fato para comunicação entre serviços, especialmente em ambientes distribuídos.
Um ponto que frequentemente gera confusão é a distinção entre o padrão MVC e a arquitetura em três camadas (N-Tier). O diagrama abaixo ilustra como esses conceitos se relacionam e como são aplicados na prática usando o Spring MVC como exemplo.
O Spring implementa o padrão MVC para estruturar a interface de entrada da aplicação (principalmente em aplicações web), enquanto simultaneamente incentiva uma separação clara entre Controller, Service e Repository, formando uma arquitetura em camadas típica.
Press enter or click to view image in full size

Esse arranjo é flexível: os controllers podem retornar diferentes formatos de dados, como JSON, XML, ou outros, dependendo do tipo de resposta desejada.
Contudo, o retorno de JSON não implica necessariamente que estamos utilizando REST, assim como o uso de XML não implica automaticamente que estamos lidando com SOAP. Essas são apenas formas de representação de dados. O protocolo ou estilo arquitetural adotado vai além dessa escolha. Ou seja, nem todo endpoint que retorna JSON é REST. Nem toda resposta XML é SOAP. Esses são conceitos distintos que merecem uma discussão mais aprofundada, assunto para outro artigo.
Estilo Hexagonal: Flexibilidade Arquitetural
Em 2005, Alistair Cockburn publicou o conceito de Arquitetura Hexagonal, também conhecida como Ports and Adapters, no relatório técnico Hat — Technical Report 2005 (HaT — Human and Technology), desenvolvido pela consultoria do próprio Alistair.
Como estilo arquitetural, a abordagem hexagonal oferece princípios orientadores sem prescrever implementações específicas. O conceito une: isolamento do domínio, separação de responsabilidades, e independência tecnológica, com ênfase em inversão de controle e inversão de dependência.
A Inversão de Dependência (DIP), um dos princípios do SOLID, afirma que módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações, tipicamente por meio de interfaces. As implementações concretas dessas interfaces são injetadas através de Inversão de Controle (IoC), permitindo flexibilidade, testabilidade e maior desacoplamento entre as camadas.
Como a Arquitetura Hexagonal Funciona
O mecanismo é elegante: toda a interação com o domínio, ou do domínio com o mundo externo, ocorre por meio de portas (ports). Em termos práticos, em linguagens como Java ou C#, essas portas são implementadas como interfaces. As implementações concretas dessas interfaces são chamadas de adapters.
Um ponto importante para destacar é que as portas fazem parte do domínio. Elas servem como a interface para interagir com ele. Além disso, essa estrutura permite testar o domínio de forma isolada, já que as implementações dos adapters ficam fora do domínio. Isso garante o isolamento do domínio de implementações técnicas externas.
Outro aspecto fundamental a ser entendido é que a aplicação de ports e adapters não se limita apenas à “troca de banco de dados”. Ports e adapters são uma forma de estruturar a interação da aplicação com o mundo externo. Por exemplo, se antes a aplicação possuía apenas um ponto de entrada via API REST, e agora ela precisa se integrar a outro sistema através de mensageria ou streams, você só precisa implementar um novo adapter e conectá-lo à sua arquitetura usando a mesma “porta de entrada”. Isso mostra que a abordagem não se resume a facilitar a troca de uma implementação por outra, isso simplificaria demais o poder desse conceito.

Essa abordagem oferece intercambialidade completa, permitindo flexibilidade total de implementação. Não se trata apenas de “trocar banco de dados”, mas de permitir ajustes finos em diversos aspectos da arquitetura com pouca ou sem mudanças no domain. Exemplos práticos dessa flexibilidade incluem:
- Remover o ORM e usar JDBC puro para uma otimização específica de performance.
- Implementar uma camada de cache sem impactar o domínio central.
- Aplicar feature toggles para deploy canário ou realizar testes A/B.
- Customizar implementações para contextos específicos, como novos tipos de integração ou mudanças nas necessidades de desempenho.
Um aspecto importante a ser destacado é que a Arquitetura Hexagonal, por ser um estilo, não é prescritiva quanto à implementação específica de ports e adapters. Ou seja, ela não vai falar como organizar seu código, que você use determinados diretórios, pacotes ou nomes de classes. O foco está na aplicação dos princípios e fundamentos das boas práticas, como a separação de responsabilidades e o isolamento do domínio. Por exemplo, em uma linguagem como Python, que não possui interfaces de forma nativa, você pode implementar ports usando protocolos ou classes abstratas para definir contratos, garantindo a mesma flexibilidade de interação com o domínio. Isso reforça o poder do modelo: ele se adapta à natureza do projeto e das tecnologias utilizadas, permitindo que os princípios da arquitetura sejam seguidos, independentemente da linguagem ou framework.
Por que Hexagonal?
A escolha do nome hexagonal para essa arquitetura não é apenas uma questão estética, mas também funcional. Alistair Cockburn, ao introduzir a Arquitetura Hexagonal em seu relatório técnico “Hat — Technical Report 2005”, explicou que o formato hexagonal foi selecionado porque ele oferece um espaço visual claro e eficiente para representar múltiplos ports e adapters conectados ao domínio. Esse formato supera as limitações dos diagramas tradicionais em camadas, permitindo uma representação mais flexível e menos rígida das interações dentro do sistema.
Além disso, o nome hexagonal não só cumpre uma função visual, mas também se tornou um elemento de marketing eficaz. Sua forma e a associação com o conceito ajudam a tornar o modelo memorizável e distintivo, facilitando sua adoção e disseminação.
O Perigo de Reduzir Conceitos a Soluções Simplistas
No desenvolvimento de software, uma tendência comum é aplicar conceitos amplamente aceitos sem considerar as especificidades do problema. Isso frequentemente leva à super simplificação de conceitos como acoplamento e duplicação, criando soluções que, em vez de simplificar o sistema, tornam-no mais complexo e difícil de manter.
Quando o Excesso de Desacoplamento Complica
Embora o desacoplamento seja considerado uma boa prática para melhorar a flexibilidade e a testabilidade do sistema, aplicar o conceito de “todo acoplamento é ruim” de forma rigorosa pode ser prejudicial. Em alguns casos, uma dependência controlada entre componentes é não apenas inevitável, mas necessária para garantir a coesão e o bom funcionamento do sistema.
“Programas são feitos para serem lidos por humanos e, incidentalmente, para serem executados por computadores. Quanto mais um programa for projetado para ser lido, mais fácil será mantê-lo.”— Martin Fowler, Refactoring: Improving the Design of Existing Code
Por exemplo, desacoplar excessivamente componentes pode resultar em um sistema fragmentado, no qual cada parte precisa de interfaces complexas para se comunicar. Isso aumenta a complexidade do código e a curva de aprendizado para os desenvolvedores, que precisam entender como todas as partes se conectam. Em vez de buscar o desacoplamento a todo custo, a prática mais eficaz é avaliar qual nível de acoplamento é necessário para garantir a interação coesa entre os componentes.
Duplicação: Quando Pode Ser a Melhor Solução
Da mesma forma, a ideia de que toda duplicação é ruim é uma simplificação excessiva. Embora a duplicação de código em larga escala seja um antipadrão, há cenários onde a duplicação controlada pode ser benéfica. Em alguns casos, duplicar uma pequena porção de código pode ser mais simples e eficiente do que criar uma abstração complexa, que muitas vezes torna o código mais difícil de entender e manter.
Por exemplo, se há uma mudança local que precisa ser feita em várias partes do sistema, pode ser mais prático duplicar o código em vez de refatorar todo o sistema para uma abordagem centralizada. A duplicação, nesse caso, pode melhorar a legibilidade e a manutenibilidade, além de reduzir o risco de introduzir bugs complexos ao modificar uma parte central do sistema. Essa abordagem pragmática é focada em resolver problemas imediatos de forma eficiente, sem buscar um ideal abstrato.
“A duplicação não é, em si, um mal. O problema é quando a duplicação é desnecessária. Em vez de evitar a duplicação a todo custo, devemos buscar um equilíbrio: eliminar a duplicação quando ela criar complexidade ou inconsistência, mas permitir uma duplicação controlada onde ela facilite a manutenção ou a legibilidade do código.” — Martin Fowler, Refactoring: Improving the Design of Existing Code
O segredo não está em seguir as boas práticas de forma cega, mas em adaptá-las ao contexto do sistema. Desacoplar é importante, mas não até o ponto de tornar o sistema fragmentado e difícil de entender. Duplicar pode ser um problema quando é desnecessário, mas, em algumas situações, pode ser uma solução prática e eficiente, permitindo que o código seja mais legível e fácil de manter. Em ambos os casos, a chave está em avaliar as necessidades específicas do projeto e aplicar as melhores práticas com pragmatismo.
Como Martin Fowler corretamente aponta, o design de software não deve ser uma busca cega por teorias abstratas, mas sim uma aplicação equilibrada de princípios, ajustados conforme os requisitos e o contexto do sistema. A flexibilidade para adaptar conceitos ao mundo real é o que faz um bom design de software se destacar.
A compreensão e aplicação dessas práticas não devem ser dogmáticas. Cada decisão arquitetural deve ser motivada pela necessidade de resolver um problema real, e não pela simples aplicação de um princípio teórico. Ao focar nas necessidades do sistema e nas facilidades de manutenção e legibilidade, podemos criar soluções que, longe de simplificar demais, agregam valor e clareza ao longo do tempo.
“A arte da programação é a arte de organizar a complexidade, de dominar a multiplicidade e evitar seu caos bastardo da forma mais eficaz possível” — Edsger Dijkstra
Conclusão
MVC, arquitetura em camadas e Hexagonal não são adversários em um ringue arquitetural. São ferramentas complementares que resolvem problemas em diferentes dimensões. MVC organiza a apresentação, camadas estruturam responsabilidades, e Hexagonal isola o domínio. A maestria está em reconhecer quando cada um serve melhor ao sistema.
O verdadeiro insight para arquitetos experientes é compreender que não existe arquitetura perfeita, apenas arquitetura adequada. Cada decisão carrega trade-offs que ecoarão pelos próximos anos do sistema. A diferença de maturidade do arquitetos não está no conhecimento de padrões, mas na sabedoria de aplicá-los com pragmatismo.
Como Dijkstra nos lembra, programar é “a arte de organizar a complexidade”. Em um mundo onde a complexidade cresce exponencialmente, nossa responsabilidade como arquitetos não é eliminá-la. É saber fazer boas escolhas. Fazemos isso não seguindo dogmas, mas construindo sistemas que humanos possam compreender, modificar e evoluir.
A arquitetura de software é, no fim das contas, uma conversa entre o presente e o futuro. Cada linha de código que escrevemos hoje é uma carta para os desenvolvedores de amanhã. Que essa carta seja clara, respeitosa e, acima de tudo, útil.
Frameworks mudam, tecnologias se tornam obsoletas, requisitos evoluem. O que sobrevive são as decisões estruturais bem fundamentadas e a facilidade de mudança que construímos no sistema.
O código que escrevemos hoje é o legado que deixamos para nós e quem virá depois de nós.