MÓDULO 15 · CONCEITO 01 DE 12

Estilos Arquiteturais — o mapa antes da escolha

O que é estilo arquitetural, como se distingue de padrão de design, e os critérios que determinam qual estilo serve a cada problema

Tempo de leitura ~20 min Pré-requisito Módulo 00 · Fundamentos de Código Próximo 02 · Layered Architecture

Toda decisão de arquitetura começa com uma escolha implícita ou explícita de estilo. Você pode chamar de framework mental, de template estrutural, ou simplesmente de "como vamos organizar esse sistema". O nome que a comunidade consolida é estilo arquitetural: um conjunto de decisões estruturais recorrentes que moldam a forma geral de um sistema — como seus elementos são organizados, como se comunicam, e quais propriedades emergem dessa organização.

A confusão mais comum é misturar estilo arquitetural com padrão de design. Um padrão de design (Factory Method, Observer, Strategy) é uma solução para um problema local e repetível — dentro de uma classe, entre duas classes, em um módulo. Um estilo arquitetural opera em escala maior: define a estrutura do sistema inteiro, os papéis dos seus grandes blocos, e as regras de dependência entre eles. Outro nível acima está a decisão de infraestrutura: onde o sistema roda, como é deployado, quais serviços externos usa. As três camadas coexistem e se complementam, mas operam em escalas diferentes.

Os estilos principais e o que os define

Layered (em camadas) pergunta: como separo responsabilidades por nível de abstração? Presentation depende de Application; Application depende de Domain; Domain não depende de ninguém abaixo. O modelo mental é intuitivo, o onboarding é rápido, e o custo é a tendência de as camadas vazarem ao longo do tempo.

Hexagonal (Ports & Adapters) pergunta: como isolo o domínio de tudo que é tecnologia? O domínio fica no centro; HTTP, banco, filas e email são adaptadores. O sistema todo pode ser testado sem banco, sem HTTP, trocando adaptadores reais por fakes.

Clean Architecture pergunta o mesmo que Hexagonal, mas adiciona um anel de Use Cases explícito entre o domínio puro (Entities) e o mundo externo. A contribuição de Robert Martin (2012) foi formalizar que Use Cases são o coração de uma aplicação, e que frameworks, banco, e UI são detalhes.

Microservices pergunta: como permito que times diferentes deployem de forma independente? O custo é alto: comunicação via rede, consistência eventual, debugging distribuído. Microservices não resolvem problema técnico — resolvem problema organizacional.

Event-Driven pergunta: como permito que partes do sistema evoluam de forma desacoplada no tempo? Componentes produzem eventos; outros consomem sem que o produtor precise conhecer o consumidor.

Monolito modular não é um estilo arquitetural formal, mas é uma resposta pragmática: a disciplina de delimitar módulos com interfaces claras dentro de um único processo. A maioria dos sistemas deveria começar aqui.

Os critérios de escolha

Complexidade do domínio. Quando as regras de negócio são ricas e mudam com frequência, o domínio precisa de proteção estrutural. Hexagonal ou Clean Architecture permitem que o domínio evolua sem ser contaminado por decisões de tecnologia.

Acoplamento e mudança. A pergunta prática é: quando um requisito muda, quantos arquivos você precisa tocar? O estilo arquitetural determina em grande parte a topologia de dependências — e, por extensão, a topologia das mudanças.

Testabilidade. O estilo arquitetural define o custo de testar. Em Layered com Domain chamando Infrastructure diretamente, testar o domínio requer banco. Em Hexagonal, o domínio é testável sem nenhuma dependência externa.

Tamanho e estrutura de equipe. Conway's Law: os sistemas que equipes produzem refletem a estrutura de comunicação da equipe. Microservices fazem mais sentido quando times precisam de autonomia de deploy. Monolito modular faz mais sentido quando um time pequeno precisa de velocidade.

regra prática

Se você não consegue articular qual problema o estilo arquitetural escolhido resolve para o sistema em questão, você está cargo-culting. Estilos são ferramentas de vocabulário e de estruturação — eles existem para resolver problemas específicos, não para demonstrar sofisticação técnica.

Exemplos comparativos: Layered vs Hexagonal no mesmo serviço

C# — Layered vs Hexagonal
// Layered — Domain chama Infrastructure diretamente (acoplamento)
public class PaymentService
{
    private readonly PaymentRepository _repo; // classe concreta de Infra
    private readonly EmailSender _email;

    public async Task ProcessAsync(Payment payment)
    {
        payment.Validate();
        await _repo.SaveAsync(payment); // acoplamento direto à infra
        await _email.SendConfirmationAsync(payment.CustomerId);
    }
}

// Hexagonal — Domain fala com ports (interfaces), nunca com infra
public interface IPaymentRepository { Task SaveAsync(Payment p); }
public interface INotifier { Task NotifyAsync(CustomerId id, string msg); }

public class PaymentService
{
    private readonly IPaymentRepository _repo; // port
    private readonly INotifier _notifier;      // port

    public async Task ProcessAsync(Payment payment)
    {
        payment.Validate(); // Domain puro
        await _repo.SaveAsync(payment);
        await _notifier.NotifyAsync(payment.CustomerId, "confirmed");
    }
}
// Em teste: injetar FakePaymentRepository e FakeNotifier — sem banco

Em Layered, o Domain importa tipos concretos de Infrastructure — testar a lógica de negócio exige banco real. Em Hexagonal, o Domain só conhece interfaces que ele mesmo definiu; qualquer adapter que satisfaça o contrato serve — incluindo fakes em memória para testes.

Python — Layered vs Hexagonal
# Layered — domínio acoplado à infraestrutura
class PaymentService:
    def __init__(self, db_session, smtp_client):
        self._db = db_session    # infra concreta
        self._smtp = smtp_client

    async def process(self, payment: Payment) -> None:
        payment.validate()
        await self._db.save(payment) # acoplamento direto

# Hexagonal — ports como ABCs (contratos do domínio)
from abc import ABC, abstractmethod

class PaymentRepository(ABC):
    @abstractmethod
    async def save(self, payment: Payment) -> None: ...

class Notifier(ABC):
    @abstractmethod
    async def notify(self, customer_id: str, msg: str) -> None: ...

class PaymentService:
    def __init__(self, repo: PaymentRepository, notifier: Notifier):
        self._repo = repo
        self._notifier = notifier

    async def process(self, payment: Payment) -> None:
        payment.validate()  # domínio puro
        await self._repo.save(payment)
        await self._notifier.notify(payment.customer_id, "confirmed")

# Em teste: FakePaymentRepository(PaymentRepository) com lista em memória

Python não tem interfaces nativas — ABCs (Abstract Base Classes) ou typing.Protocol cumprem o papel de ports. Protocol é mais pythônico (duck typing estrutural, sem herança obrigatória). ABC é mais explícito sobre o contrato esperado.

Go — Layered vs Hexagonal
// Layered — Service depende de structs concretas de infra
type PaymentService struct {
    repo  *PostgresPaymentRepo  // concreto
    email *SMTPEmailSender
}

// Hexagonal — Service depende de interfaces (ports implícitas em Go)
type PaymentRepository interface {
    Save(ctx context.Context, p Payment) error
}
type Notifier interface {
    Notify(ctx context.Context, customerID string, msg string) error
}

type PaymentService struct {
    repo     PaymentRepository
    notifier Notifier
}

func (s *PaymentService) Process(ctx context.Context, p Payment) error {
    if err := p.Validate(); err != nil { return err }
    if err := s.repo.Save(ctx, p); err != nil { return err }
    return s.notifier.Notify(ctx, p.CustomerID, "confirmed")
}
// Em teste: FakePaymentRepository com map[string]Payment em memória

Go favorece Hexagonal naturalmente: interfaces são satisfeitas implicitamente (duck typing), então qualquer struct com os métodos certos é um adapter válido — sem declaração explícita. Isso torna a criação de fakes para teste especialmente simples.

A armadilha da arquitetura prematura

A tentação em todo projeto novo é decidir a arquitetura antes de entender o domínio. O resultado é comum: uma estrutura grandiosa que acomoda casos de uso que nunca existiram, com abstrações que ninguém consegue explicar sem o contexto original da decisão. A sabedoria consolidada aponta para uma abordagem diferente: comece com o estilo mais simples que serve ao problema atual. Refatore quando sentir o custo do acoplamento.

Referências para aprofundar

  1. livro Patterns of Enterprise Application Architecture — Martin Fowler. Addison-Wesley, 2002. Caps 1-3: o vocabulário fundamental de arquitetura. A origem de muitos padrões que os estilos modernos refinaram.
  2. livro Software Architecture in Practice, 4ª ed. — Bass, Clements, Kazman. Addison-Wesley, 2021. O livro canônico de atributos de qualidade e estilos arquiteturais. Define testabilidade, modificabilidade e disponibilidade como atributos de qualidade mensuráveis.
  3. livro Fundamentals of Software Architecture — Richards, Ford. O'Reilly, 2020. Cobertura ampla de estilos com trade-offs explícitos em tabelas comparativas. Útil como referência rápida antes de uma decisão arquitetural.
  4. livro Clean Architecture — Robert C. Martin. Prentice Hall, 2017. Caps 1-7: o argumento para separar domínio de detalhe. A premissa central: banco de dados, UI e frameworks são detalhes que devem ficar nas bordas.
  5. artigo Hexagonal Architecture — Alistair Cockburn. alistair.cockburn.us, 2005. O artigo original que definiu Ports & Adapters. Curto, mas denso — vale a leitura da fonte primária para entender a motivação original.
  6. livro Building Microservices, 2ª ed. — Sam Newman. O'Reilly, 2021. Cap 1: quando microservices fazem sentido. Um dos capítulos mais honestos sobre os pré-requisitos e o custo real do estilo.
  7. artigo Microservices — Martin Fowler. martinfowler.com, 2014. O artigo que popularizou o termo. Ainda relevante como baseline — especialmente a seção de prerequisites que muita gente ignora.
  8. paper How do committees invent? — Melvin Conway. Datamation, 1968. A observação empírica por trás da Conway's Law. Menos de quatro páginas; explica por que estrutura de time e estrutura de sistema convergem.
  9. livro Domain-Driven Design — Eric Evans. Addison-Wesley, 2003. A origem do vocabulário de domínio. Os estilos arquiteturais modernos como Hexagonal e Clean Architecture foram amplamente influenciados pelo DDD.
  10. livro Working Effectively with Legacy Code — Michael Feathers. Prentice Hall, 2004. A perspectiva prática de quem precisa mudar arquitetura existente. Cap 1-3: por que testes são a única rede de segurança em refactorings arquiteturais.
  11. livro Designing Data-Intensive Applications — Martin Kleppmann. O'Reilly, 2017. Cap 1: propriedades fundamentais de sistemas — confiabilidade, escalabilidade, manutenibilidade. O pano de fundo para qualquer decisão de estilo.
  12. livro Enterprise Integration Patterns — Hohpe, Woolf. Addison-Wesley, 2003. O vocabulário de integração entre estilos. Essencial quando múltiplos bounded contexts ou serviços precisam se comunicar.