Arquitetura

MVC

Separa uma aplicação em três responsabilidades distintas — Model (dados e regras de negócio), View (apresentação) e Controller (entrada e fluxo) — permitindo que cada camada evolua de forma independente.

Intenção

Separar as preocupações de dados, apresentação e controle de fluxo em três componentes distintos, de modo que alterações em um não forcem modificações nos outros. O Model não sabe como a View exibe os dados; a View não sabe de onde os dados vêm; o Controller coordena os dois sem conter lógica de negócio.

MVC foi criado por Trygve Reenskaug no Xerox PARC, no final dos anos 1970 (no contexto do Smalltalk), como uma solução para construir interfaces gráficas interativas. Desde então migrou para aplicações desktop, depois para a web server-side (Rails, Django, Laravel, ASP.NET MVC) e, mais recentemente, influenciou variantes como MVP e MVVM usadas em desenvolvimento mobile e front-end.

Problema

Sem separação de responsabilidades, código de apresentação (HTML, formatação), regras de negócio (validação, cálculos) e lógica de controle (roteamento, autenticação) ficam misturados — o que se conhece como spaghetti code ou, no extremo web, o problema do "controlador gordo" ou da "view com SQL". As consequências práticas são:

  • Dificuldade de testar: não é possível testar regras de negócio sem renderizar HTML ou sem fazer requisições HTTP.
  • Reaproveitamento zero: a mesma lógica de negócio não pode ser reutilizada por uma API JSON e por uma interface web.
  • Alta fragilidade: uma mudança no banco de dados quebra o template; uma mudança no layout quebra a query.

MVC ataca exatamente esse problema ao definir fronteiras claras: o Model encapsula o estado e as regras; a View sabe apenas como renderizar; o Controller apenas traduz a intenção do usuário em chamadas ao Model e em seleção de View.

Estrutura

MVC clássico (Smalltalk / desktop)

No MVC original, a View observa diretamente o Model e se atualiza quando o Model muda — uma aplicação do padrão Observer. O Controller traduz a entrada do usuário (teclado, mouse) em comandos ao Model.

  ┌─────────────────────────────────────────────────────┐
  │                    Usuário                          │
  │              (interage com a View)                  │
  └──────────────────────┬──────────────────────────────┘
                         │ entrada (clique, tecla)
                         ▼
                   ┌───────────┐
                   │Controller │ ←── interpreta a ação
                   └─────┬─────┘
                         │ atualiza
                         ▼
                   ┌───────────┐
                   │  Model    │ ←── dados + regras de negócio
                   └─────┬─────┘
                         │ notifica (Observer)
                         ▼
                   ┌───────────┐
                   │   View    │ ←── lê o Model e re-renderiza
                   └───────────┘

  Dependências:
    Controller → Model  (atualiza estado)
    View → Model        (lê estado para renderizar)
    Model → View        (notifica via Observer — sem conhecer a View concreta)

MVC web / server-side

Na web, o protocolo HTTP é stateless: não há um canal de notificação persistente do Model para a View. A relação Observer desaparece. O fluxo é sempre request-response e unidirecional: o Controller processa a requisição, consulta ou modifica o Model, seleciona uma View e a renderiza com os dados.

  Requisição HTTP
       │
       ▼
  ┌────────────────────────────────────────────────────────┐
  │                    Controller                          │
  │  - recebe e valida a requisição                        │
  │  - chama o(s) Model(s) necessário(s)                   │
  │  - seleciona a View apropriada                         │
  │  - repassa os dados para a View renderizar             │
  └────────┬───────────────────────────┬───────────────────┘
           │ usa                       │ passa dados para
           ▼                           ▼
     ┌───────────┐               ┌───────────┐
     │  Model    │               │   View    │
     │           │               │           │
     │ - estado  │               │ - template│
     │ - regras  │               │ - HTML    │
     │ - queries │               │ - JSON    │
     └───────────┘               └─────┬─────┘
                                       │
                                       ▼
                               Resposta HTTP

  Dependências no MVC web:
    Controller → Model  (lê/escreve dados)
    Controller → View   (seleciona e alimenta)
    View → Model        (apenas dados passados pelo Controller — sem referência direta)
    Model               (não conhece Controller nem View)

Como funciona

Model

O Model representa o estado da aplicação e contém as regras de negócio. Ele não sabe nada sobre como os dados serão exibidos nem de onde veio a requisição. Em aplicações web, o Model costuma envolver entidades de domínio, repositórios de acesso a dados e serviços de domínio.

Um Model bem projetado é testável de forma independente — sem HTTP, sem template, sem framework web. Sua interface pública expõe comportamentos (pedido.calcularTotal(), usuario.podePublicar()), não apenas dados nus.

View

A View transforma os dados fornecidos pelo Controller em uma representação consumível pelo usuário: HTML, JSON, XML, PDF. Ela não contém lógica de negócio — no máximo, lógica de apresentação (formatação de datas, pluralização, condicionais de exibição baseadas em flags passadas pelo Controller).

Em frameworks modernos, a View é um template (Blade, Twig, ERB, Razor) ou um componente de UI. O princípio é o mesmo: a View é passiva — recebe dados e os renderiza.

Controller

O Controller é o mediador. Ele recebe a entrada (requisição HTTP, evento de UI), valida superficialmente os parâmetros, delega o trabalho real ao Model e seleciona qual View usar com quais dados. O Controller deve ser fino: se há lógica de negócio no Controller, ela está no lugar errado.

Quando usar

  • Aplicações web server-side com renderização de templates: é o padrão dominante em frameworks como Laravel, Rails, Django, ASP.NET MVC e Spring MVC. A separação Model/View/Controller é nativa na estrutura do framework.
  • Quando o mesmo Model precisa ser exposto por múltiplas Views: uma API JSON e uma interface HTML podem compartilhar o mesmo Model — apenas o Controller e a View mudam.
  • Para equipes que dividem responsabilidades: back-end cuida do Model, front-end cuida dos templates (View), e o Controller define o contrato entre os dois.

Quando evitar

  • Aplicações com lógica de domínio complexa: MVC por si só não define onde fica a lógica de aplicação, os casos de uso, as regras de autorização por caso de uso. Para sistemas complexos, MVC costuma ser o ponto de partida, mas precisa ser complementado com Arquitetura em Camadas ou Arquitetura Hexagonal para que o domínio não vaze para o Controller.
  • SPAs (Single Page Applications): quando o front-end é gerenciado por React, Vue ou Angular, o MVC server-side perde relevância para a camada de apresentação. O servidor passa a ser uma API REST/GraphQL — o padrão relevante fica no cliente (MVVM com reatividade, Flux, etc.).
  • Quando a "View" é uma API JSON simples sem estado de UI: o conceito de View como template não se aplica. Uma API pura é melhor modelada com uma camada de Application Service + serialização explícita.

Prós e contras

Prós

  • Separação clara de responsabilidades: Model, View e Controller evoluem de forma independente.
  • Model totalmente testável sem infraestrutura de apresentação.
  • Facilita o trabalho paralelo: desenvolvedores de back-end e front-end podem atuar em paralelo com um contrato definido.
  • Suporte nativo em praticamente todos os frameworks web modernos — a curva de aprendizado é baixa para quem já conhece o framework.
  • Múltiplas Views para o mesmo Model sem duplicação de lógica.

Contras

  • Não define onde fica a lógica de aplicação (casos de uso, orquestração) — essa lacuna costuma ser preenchida mal, engordando o Controller.
  • Em aplicações grandes, a separação MVC é insuficiente: é preciso combinar com Arquitetura em Camadas ou Hexagonal para isolar o domínio da infraestrutura.
  • O conceito de "View" é ambíguo em APIs REST puras — o padrão se aplica com adaptações.
  • Pode dar uma falsa sensação de organização: ter pastas models/, views/ e controllers/ não garante que a lógica esteja nos lugares certos.

Armadilhas comuns

1. Fat Controller (Controlador gordo)

A armadilha mais comum: toda a lógica de negócio e de orquestração vai parar no Controller porque é o lugar "mais fácil" de adicionar código. O Controller passa a ter centenas de linhas com validações, cálculos, chamadas ao banco, envio de e-mails e formatação de resposta. O resultado é um Controller que é impossível de testar unitariamente e impossível de reutilizar.

Regra prática: se o Controller faz mais do que (1) validar parâmetros de entrada, (2) chamar um serviço/caso de uso e (3) selecionar a resposta, a lógica excedente pertence ao Model ou a um serviço de aplicação. Controllers devem ser finos o suficiente para serem dispensáveis nos testes de regra de negócio.

2. Lógica de negócio na View

Templates com condicionais complexas, cálculos de totais, aplicação de descontos e validações de permissão indicam que a View foi sobrecarregada. A View deve receber dados já prontos para exibição — ou, no máximo, dados com flags de apresentação (podeEditar: true) calculadas pelo Controller ou pelo Model.

3. Anemic Model (Model anêmico)

O oposto do fat controller: o Model se torna apenas um recipiente de dados (getters e setters), sem nenhum comportamento. Toda a lógica vai para o Controller ou para classes de serviço. O resultado é que o "Model" não modela nada — é apenas um DTO. Prefira modelos ricos que encapsulam invariantes e comportamentos relevantes ao domínio.

4. Acoplamento direto entre View e banco de dados

Queries SQL ou chamadas a ORM dentro de templates (o clássico problema N+1 nas views de PHP legado) são o sinal mais claro de que as responsabilidades colapsaram. Toda consulta deve passar pelo Model; a View recebe apenas coleções já resolvidas.

Arquiteturas e padrões relacionados

O Observer é o mecanismo que permite ao Model notificar as Views no MVC clássico sem depender delas concretamente — o Model dispara eventos; as Views, registradas como observadoras, se atualizam. Na web server-side essa relação desaparece, pois não há canal persistente.

A Arquitetura em Camadas complementa o MVC ao definir como o Model se estrutura internamente: Apresentação (Controller + View), Aplicação (casos de uso), Domínio (entidades e regras) e Infraestrutura (banco, APIs externas). MVC sem camadas claras no Model costuma resultar em fat controllers.

A Arquitetura Hexagonal vai além e inverte as dependências: o domínio define portas (interfaces), e os adaptadores (Controllers HTTP, adaptadores de banco) implementam essas portas. O Controller MVC se torna um adaptador primário — e o domínio permanece completamente isolado da web.

MVP (Model-View-Presenter) e MVVM (Model-View-ViewModel) são evoluções do MVC para contextos de UI reativos (Android, iOS, front-end com frameworks declarativos). O Presenter e o ViewModel assumem o papel de mediar a View de forma mais bidirecional do que o Controller MVC clássico permite.