MVVM e MVP: variações para frontend

1. Introdução aos Padrões de Apresentação no Frontend

1.1. O problema da separação entre lógica de negócio e interface

Um dos desafios mais persistentes no desenvolvimento frontend é a mistura indevida entre lógica de apresentação e lógica de negócio. Quando regras de domínio, validações e estado da interface coexistem no mesmo componente, o código torna-se frágil, difícil de testar e propenso a efeitos colaterais inesperados. A separação clara entre essas camadas não é uma questão estética — é uma decisão arquitetural que impacta diretamente a manutenibilidade do sistema.

1.2. Contexto histórico: do MVC clássico aos padrões modernos

O padrão MVC (Model-View-Controller) surgiu na década de 1970 no Smalltalk-80 e estabeleceu a base para a separação de responsabilidades em interfaces gráficas. No entanto, sua implementação no frontend web mostrou limitações: o Controller frequentemente se acoplava demais à View, e a lógica de apresentação ficava difusa entre os três componentes. Padrões como MVP e MVVM surgiram como variações que endereçam essas fragilidades, adaptando-se a diferentes paradigmas de framework e requisitos de testabilidade.

1.3. Objetivo comum: testabilidade, manutenibilidade e responsabilidade única

Tanto MVP quanto MVVM compartilham o mesmo objetivo fundamental: isolar a lógica de apresentação da interface concreta, permitindo que ela seja testada independentemente de componentes visuais. Ambos promovem a responsabilidade única ao delegar papéis específicos para cada camada, mas divergem significativamente na forma como gerenciam o fluxo de dados e o acoplamento com a plataforma.

2. MVP (Model-View-Presenter): Estrutura e Fluxo

2.1. Papéis: Model, View (passiva) e Presenter (orquestrador)

No MVP, a View é deliberadamente passiva — ela expõe uma interface que o Presenter utiliza para manipular a exibição. O Presenter é o orquestrador central: ele recebe eventos da View, consulta e atualiza o Model, e decide exatamente o que a View deve mostrar. O Model permanece como a camada de dados e regras de negócio, sem conhecimento sobre a interface.

2.2. Fluxo de controle: View delega eventos ao Presenter, que atualiza o Model

O fluxo típico do MVP segue um ciclo fechado: o usuário interage com a View → a View notifica o Presenter → o Presenter processa a ação, interage com o Model → o Presenter atualiza a View através de sua interface. Esse fluxo explícito torna o rastreamento de bugs mais direto, pois cada transição de estado é orquestrada pelo Presenter.

2.3. Exemplo de código: interface da View e implementação do Presenter

// Interface da View (passiva)
interface UserListView {
    void showUsers(List<User> users);
    void showLoading(boolean isLoading);
    void showError(String message);
}

// Presenter (orquestrador)
class UserListPresenter {
    private final UserListView view;
    private final UserRepository repository;

    public UserListPresenter(UserListView view, UserRepository repository) {
        this.view = view;
        this.repository = repository;
    }

    public void onViewReady() {
        view.showLoading(true);
        repository.fetchUsers(new Callback<List<User>>() {
            @Override
            public void onSuccess(List<User> users) {
                view.showLoading(false);
                view.showUsers(users);
            }

            @Override
            public void onError(String error) {
                view.showLoading(false);
                view.showError(error);
            }
        });
    }

    public void onUserSelected(User user) {
        // Lógica de navegação ou ação
        if (user.isActive()) {
            view.showUserDetails(user);
        } else {
            view.showError("Usuário inativo");
        }
    }
}

3. MVVM (Model-View-ViewModel): Ligação e Reatividade

3.1. Papéis: Model, View (ativa) e ViewModel (estado e comandos)

No MVVM, a View é ativa: ela observa automaticamente as mudanças no ViewModel por meio de mecanismos de data binding. O ViewModel expõe propriedades reativas e comandos que representam o estado e as ações da interface, sem referência direta à View. O Model continua sendo a camada de domínio, e o ViewModel atua como um adaptador que transforma dados do Model em algo consumível pela View.

3.2. Mecanismo central: data binding bidirecional e observáveis

O coração do MVVM é o sistema de observação. Quando uma propriedade no ViewModel muda, a View é automaticamente atualizada. Quando o usuário interage com a View, os valores são refletidos de volta no ViewModel. Esse mecanismo elimina a necessidade de código explícito de sincronização, mas introduz complexidade no rastreamento do fluxo de dados.

3.3. Exemplo de código: ViewModel com propriedades reativas e binding na View

// ViewModel com propriedades observáveis
class UserListViewModel {
    private ObservableList<User> users = new ObservableArrayList<>();
    private ObservableBoolean isLoading = new ObservableBoolean(false);
    private ObservableString errorMessage = new ObservableString("");
    private final UserRepository repository;

    public UserListViewModel(UserRepository repository) {
        this.repository = repository;
    }

    public void loadUsers() {
        isLoading.set(true);
        errorMessage.set("");
        repository.fetchUsers(new Callback<List<User>>() {
            @Override
            public void onSuccess(List<User> result) {
                users.setAll(result);
                isLoading.set(false);
            }

            @Override
            public void onError(String error) {
                isLoading.set(false);
                errorMessage.set(error);
            }
        });
    }

    public ObservableList<User> usersProperty() { return users; }
    public ObservableBoolean isLoadingProperty() { return isLoading; }
    public ObservableString errorMessageProperty() { return errorMessage; }
}

// Exemplo de binding na View (pseudo-código)
// <ListView items="{viewModel.usersProperty}" />
// <Label text="{viewModel.errorMessageProperty}" visible="{viewModel.isLoadingProperty}" />

4. Comparação Direta: MVP vs MVVM

4.1. Quem controla a lógica de apresentação? (Presenter vs ViewModel)

No MVP, o Presenter detém o controle total sobre o fluxo de apresentação. Ele decide quando e como a View deve ser atualizada. No MVVM, o ViewModel expõe estado, mas não controla diretamente quando a View será atualizada — isso fica a cargo do mecanismo de binding. Essa diferença fundamental impacta a previsibilidade do comportamento.

4.2. Acoplamento com a plataforma: View passiva vs View ativa

O MVP tende a ter menor acoplamento com frameworks específicos, pois a View é uma interface que pode ser implementada em qualquer tecnologia. O MVVM, por outro lado, depende fortemente de um sistema de binding reativo, o que pode amarrá-lo a um ecossistema específico (Angular, Vue, Knockout).

4.3. Impacto na testabilidade e na complexidade do código

Ambos os padrões oferecem boa testabilidade, mas de formas diferentes. No MVP, testar o Presenter é trivial: basta fornecer um mock da View e verificar as chamadas. No MVVM, testar o ViewModel envolve verificar mudanças em propriedades observáveis, o que pode exigir configuração adicional de frameworks de teste.

5. Quando Usar MVP no Frontend

5.1. Cenários ideais: frameworks sem binding nativo, aplicações legadas

MVP é particularmente adequado quando o framework não oferece suporte nativo a data binding, como em aplicações jQuery, GWT ou projetos Android nativos tradicionais. Também é uma escolha natural para sistemas legados que precisam de modernização gradual.

5.2. Vantagens: controle explícito do fluxo, fácil depuração

O fluxo explícito do MVP facilita a depuração: cada transição de estado passa pelo Presenter, que pode ser instrumentado com logs ou breakpoints. O desenvolvedor sabe exatamente onde a lógica está sendo executada.

5.3. Desvantagens: código boilerplate, crescimento do Presenter

À medida que a View cresce em complexidade, o Presenter tende a inflar, acumulando responsabilidades demais. Isso exige disciplina para refatorar e dividir Presenters maiores em unidades menores.

// Exemplo de Presenter inchado (anti-pattern)
class UserManagementPresenter {
    // 15 métodos diferentes para validação, formatação, navegação
    public void validateEmail(String email) { ... }
    public void formatPhoneNumber(String phone) { ... }
    public void navigateToProfile(User user) { ... }
    // ... mais 12 métodos
}

6. Quando Usar MVVM no Frontend

6.1. Cenários ideais: frameworks reativos (Angular, Vue, React com hooks)

MVVM brilha em ecossistemas que já fornecem mecanismos de reatividade nativos. Angular com RxJS, Vue com sua reatividade declarativa e até React com hooks (quando combinados com bibliotecas de estado como MobX) são excelentes candidatos.

6.2. Vantagens: redução de código manual, sincronização automática

A sincronização automática entre View e ViewModel reduz significativamente o código boilerplate. Mudanças no estado do ViewModel propagam-se automaticamente para a interface, eliminando chamadas explícitas de atualização.

6.3. Desvantagens: complexidade do binding, dificuldade em rastrear fluxo

O binding implícito pode tornar o fluxo de dados nebuloso. Em aplicações complexas, identificar qual mudança de propriedade disparou uma atualização específica na View pode ser desafiador, especialmente com bindings aninhados.

// Exemplo de ViewModel com dependências ocultas
class OrderViewModel {
    // Qual propriedade está causando a atualização na View?
    // O fluxo de dados não é explícito
    @Observable
    double totalPrice;
    @Observable
    List<OrderItem> items;
    @Observable
    double discount;
}

7. Considerações Finais para Arquitetos de Software

7.1. Relação com Domain-Driven Design e Ubiquitous Language

Tanto MVP quanto MVVM podem coexistir com DDD, desde que o Model represente fielmente o domínio. O Presenter e o ViewModel atuam como adaptadores que traduzem o vocabulário ubíquo para a linguagem da interface. Em sistemas complexos, vale a pena manter os modelos de domínio puros, sem vazamento de preocupações de apresentação.

7.2. Integração com Vertical Slice Architecture e MVC

Em arquiteturas modernas como Vertical Slices, cada funcionalidade pode escolher seu padrão de apresentação. Uma slice pode usar MVP para formulários complexos e outra pode usar MVVM para painéis reativos. O importante é a consistência dentro de cada slice.

7.3. Critérios de decisão: time, tecnologia e complexidade do domínio

A escolha entre MVP e MVVM deve considerar:
- Framework: frameworks reativos favorecem MVVM; frameworks imperativos favorecem MVP
- Experiência do time: times acostumados com fluxo explícito tendem a preferir MVP
- Complexidade: interfaces com muitas validações e lógica condicional podem se beneficiar do controle explícito do MVP
- Testabilidade: ambos são testáveis, mas MVP oferece testes mais diretos sem dependência de infraestrutura de binding

Nenhum padrão é universalmente superior. O arquiteto de software deve avaliar o contexto específico do projeto, a maturidade do time e as restrições tecnológicas para tomar a decisão mais adequada.

Referências