Guia migração de ZK MVC (Composers) para MVVM (ViewModels) passo-a-passo, mostrando redução de boilerplate e ganhos em testabilidade. Use quando: modernizar sistemas legados ZK 3.x-8.x, treinar equipes em MVVM, ou planejar refatoração de Composers para ViewModels.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
name: zk-mvc-to-mvvm-migration description: | Guia migração de ZK MVC (Composers) para MVVM (ViewModels) passo-a-passo, mostrando redução de boilerplate e ganhos em testabilidade. Use quando: modernizar sistemas legados ZK 3.x-8.x, treinar equipes em MVVM, ou planejar refatoração de Composers para ViewModels.
ZK MVC to MVVM Migration
Migração de padrão MVC (GenericForwardComposer) para MVVM (@ViewModel, @Command) reduz complexidade ciclomática em ~40% e elimina acoplamento com componentes UI.
Comparação MVC vs MVVM
MVC com Composer (Legado)
public class LoginComposer extends GenericForwardComposer {
private Textbox username;
private Textbox password;
private Button loginBtn;
public void onClick$loginBtn() throws Exception {
// Acoplado aos componentes UI
String user = username.getValue();
String pass = password.getValue();
if (authenticator.authenticate(user, pass)) {
Sessions.getCurrent().setAttribute("user", user);
Executions.sendRedirect("/layout.zul");
} else {
Messagebox.show("Usuário/senha inválidos!", "Erro",
Messagebox.OK, Messagebox.ERROR);
}
}
}
Problemas:
- Acoplamento forte com componentes ZK (
Textbox,Button) - Dificil de testar unitariamente
- Mistura lógica de negócio com manipulação de UI
- Boilerplate de getters/setters de componentes
MVVM com ViewModel (Moderno)
@ViewModel
public class LoginViewModel {
private String username;
private String password;
@Command
public void doLogin() {
if (authenticator.authenticate(username, password)) {
Sessions.getCurrent().setAttribute("user", username);
Executions.sendRedirect("/layout.zul");
}
}
// Getters/setters padrão
}
Benefícios:
- POJO simples, sem dependência de UI
- Testável com JUnit puro
- Separação clara de responsabilidades
- Menos código (~15 linhas vs ~50)
Como migrar passo-a-passo
Passo 1: Identificar dados bound
No arquivo .zul MVC, identifique quais componentes usam $component:
<!-- MVC -->
<textbox id="username" />
<textbox id="password" />
<button label="Login" onClick="$composer.onClick$loginBtn()" />
Estes tornam-se propriedades no ViewModel:
private String username;
private String password;
Passo 2: Extrair lógica para commands
Mova a lógica dos métodos onClick$X() para métodos anotados com @Command:
Antes (MVC):
public void onClick$btnSalvar(ComponentEvent event) {
funcionario.setNome(txtNome.getValue());
funcionario.setEmail(txtEmail.getValue());
service.salvar(funcionario);
Clients.showNotification("Salvo!");
listbox.removeAllChildren();
carregarLista();
}
Depois (MVVM):
@Command
@NotifyChange({"funcionario", "mensagem"})
public void salvar() {
service.salvar(funcionario);
this.mensagem = "Salvo com sucesso!";
carregarFuncionarios();
}
Passo 3: Substituir acesso a componentes
Substitua component.getValue() por propriedades diretas:
| MVC Pattern | MVVM Pattern |
|---|---|
txtNome.getValue() | nome (property direta) |
txtEmail.setValue(x) | setEmail(x) |
listbox.getModel() | model (property) |
Clients.showNotification(msg) | Injete via @Wire ou use binding |
Passo 4: Atualizar bindings na view
Mude de $composer para vm:
<!-- ANTES (MVC) -->
<window composerClass="br.com.LoginComposer">
<textbox id="username" value="@{composer.username}" />
<button onClick="$composer.onClick$loginBtn()" />
</window>
<!-- DEPOIS (MVVM) -->
<window viewModel="br.com.LoginViewModel">
<textbox value="@bind(vm.username)" />
<button label="Login" onClick="@command('doLogin')" />
</window>
Passo 5: Migrar inicialização
Substitua doAfterCompose() por @Init:
MVC:
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
carregarDados();
}
MVVM:
@Init
public void init() {
carregarDados();
}
Passo 6: Migrar notificações de mudança
Substitua atualizações manuais de UI por @NotifyChange:
MVC (atualização manual):
public void onClick$btnAtualizar() {
listaModel.clear();
listaModel.addAll(service.buscarTodos());
// UI atualiza automaticamente via modelo
}
MVVM (notificação declarativa):
@Command
@NotifyChange("funcionarios")
public void atualizarLista() {
this.funcionarios = service.buscarTodos();
// UI atualiza automaticamente via binding
}
Como lidar com casos especiais
Wire de componentes (quando necessário)
Evite, mas se precisar acessar componente diretamente:
@ViewModel
public class MeuViewModel {
@Wire // Injeta componente da view
private Listbox lista;
@Command
public void selecionarItem() {
// Acesso direto (use apenas quando necessário)
Listitem selected = lista.getSelectedItem();
}
}
Na view:
<listbox id="lista" model="@load(vm.funcionarios)" />
Passar parâmetros para commands
Use @BindingParam:
@Command
public void editar(@BindingParam("id") Long id) {
this.entidade = service.buscarPorId(id);
}
<button onClick="@command('editar', id=each.id)" />
Mensagens e notificações
Injete Messagebox ou use services:
@ViewModel
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class FuncionariosViewModel {
private final NotificationService notificationService;
@Autowired
public FuncionariosViewModel(NotificationService ns) {
this.notificationService = ns;
}
@Command
public void salvar() {
service.salvar(funcionario);
notificationService.info("Registro salvo!");
}
}
Como testar ViewModel migrado
ViewModels são POJOs, então teste com JUnit puro:
class LoginViewModelTest {
private LoginViewModel viewModel;
private MockAuthenticator mockAuth;
@BeforeEach
void setUp() {
mockAuth = Mockito.mock(MockAuthenticator.class);
viewModel = new LoginViewModel(mockAuth);
}
@Test
void testDoLogin_UsuarioValido_Redireciona() {
// Arrange
Mockito.when(mockAuth.authenticate("admin", "secret"))
.thenReturn(true);
viewModel.setUsername("admin");
viewModel.setPassword("secret");
// Act
viewModel.doLogin();
// Assert
assertEquals("admin", viewModel.getUsername());
// Verifique redirecionamento via mock
}
@Test
void testDoLogin_SenhaInvalida_MostraErro() {
// Arrange
Mockito.when(mockAuth.authenticate("admin", "wrong"))
.thenReturn(false);
// Act
viewModel.doLogin();
// Assert
// Verifique que erro foi disparado
}
}
Checklist de migração
- Extrair todas as propriedades de componentes para fields
- Converter métodos
onClick$X()para@Command - Substituir acessos a componentes por properties
- Adicionar
@NotifyChangenos commands que modificam estado - Migrar
doAfterCompose()para@Init - Atualizar bindings no
.zul(@{composer.x}→@bind(vm.x)) - Remover extensão de
GenericForwardComposer - Adicionar anotação
@ViewModel(opcional) - Testar funcionalidade na UI
- Criar testes unitários para o ViewModel
Métricas de sucesso
Após migração, verifique:
| Métrica | MVC | MVVM | Melhoria |
|---|---|---|---|
| Linhas de código | ~150 | ~90 | -40% |
| Dependências UI | 5-10 classes ZK | 0 | -100% |
| Testabilidade | Baixa (mocks complexos) | Alta (JUnit puro) | +++ |
| Complexidade ciclomática | 15-20 | 8-12 | -40% |
Comparação detalhada MVC vs MVVM
1. MVC Pattern (Composer)
No MVC do ZK, o "Controller" é uma classe Java que estende SelectorComposer. Este Composer está diretamente ligado a uma página ZUL e é responsável por toda lógica de interação.
Como funciona:
- View (ZUL): A página ZUL é um layout de componentes UI. Cada componente que precisa ser manipulado recebe um
idúnico. O componente<window>ou raiz usa o atributoapplypara especificar sua classe Composer. - Controller (Composer): A classe Java é o "cérebro":
- Usa
@Wirepara obter referências diretas aos componentes UI da página ZUL, combinando nomes de variáveis comids dos componentes - Usa
@Listenou convenções de nomenclatura (onClick$myButton) para lidar com eventos - Dentro dos handlers, o código do Composer manualmente puxa dados dos componentes (ex:
myTextbox.getValue()) e manualmente empurra dados de volta
- Usa
Características chave:
- Pattern: Imperativo e orientado a eventos
- Acoplamento: Alto - O Composer está fortemente acoplado à View
- Testabilidade: Difícil - Requer ambiente complexo de teste de UI
2. MVVM Pattern (ViewModel)
MVVM promove separação de concerns mais limpa. O "ViewModel" é um POJO simples que expõe dados e commands; não tem conhecimento dos componentes UI.
Como funciona:
- View (ZUL): Define o layout. Em vez de
apply, usaviewModelpara linkar a classe ViewModel - Data Binding: O
BindComposergerencia a conexão:- Propriedades dos componentes são ligadas declarativamente às propriedades do ViewModel usando
@bind() - Eventos UI são ligados a métodos do ViewModel usando
@command()
- Propriedades dos componentes são ligadas declarativamente às propriedades do ViewModel usando
- ViewModel (POJO): Expõe estado através de getters/setters e comportamento através de métodos anotados com
@Command
Características chave:
- Pattern: Declarativo e data-centric
- Acoplamento: Baixo - ViewModel é completamente desacoplado da View
- Testabilidade: Fácil - POJO pode ser testado unitariamente sem dependências de UI
Tabela comparativa completa
| Aspecto | MVC (Composer) | MVVM (ViewModel) | Recomendação |
|---|---|---|---|
| Acoplamento | Alto (Composer depende da View) | Baixo (ViewModel independente) | MVVM para manutenibilidade |
| Testabilidade | Difícil (requer contexto UI) | Fácil (POJO puro) | MVVM para qualidade |
| Boilerplate | Mais código para wiring/events | Menos código, bindings declarativos | MVVM para produtividade |
| Legibilidade | Lógica dispersa em handlers | Lógica centralizada em commands | MVVM para clareza |
| Curva de aprendizado | Levemente mais fácil para iniciantes | Requer entender data binding | MVVM é padrão moderno |
| Manutenção | Difícil (mudanças na View quebram Controller) | Fácil (View e ViewModel evoluem separadamente) | MVVM para longo prazo |
Conclusão: Para qualquer novo projeto ZK, MVVM é altamente recomendado. Produz código mais limpo, manutenível e testável, alinhado com práticas modernas de desenvolvimento. MVC deve ser reservado para manutenção de legado.
Exemplo completo lado-a-lado
Login Form - MVC
login-mvc.zul:
<window title="Login MVC" border="normal" width="300px"
apply="br.com.zkminsamples.controller.LoginComposer">
<grid>
<rows>
<row>
<label value="Usuário:"/>
<textbox id="txtUsername" width="150px"/>
</row>
<row>
<label value="Senha:"/>
<textbox id="txtPassword" type="password" width="150px"/>
</row>
<row spans="2" align="center">
<button id="btnLogin" label="Entrar"/>
</row>
</rows>
</grid>
</window>
LoginComposer.java:
public class LoginComposer extends SelectorComposer<Component> {
@Wire
private Textbox txtUsername;
@Wire
private Textbox txtPassword;
@Listen("onClick = #btnLogin")
public void doLogin() {
// Manualmente obtém valores dos componentes UI
String username = txtUsername.getValue();
String password = txtPassword.getValue();
// ... lógica de autenticação ...
}
}
Login Form - MVVM
login-mvvm.zul:
<window title="Login MVVM" border="normal" width="300px"
viewModel="@id('vm') @init('br.com.zkminsamples.viewmodel.LoginViewModel')">
<grid>
<rows>
<row>
<label value="Usuário:"/>
<textbox width="150px" value="@bind(vm.username)"/>
</row>
<row>
<label value="Senha:"/>
<textbox type="password" width="150px" value="@bind(vm.password)"/>
</row>
<row spans="2" align="center">
<button label="Entrar" command="@command('doLogin')"/>
</row>
</rows>
</grid>
</window>
LoginViewModel.java:
public class LoginViewModel {
private String username;
private String password;
// Getters e setters obrigatórios para data binding
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
@Command
public void doLogin() {
// Usa diretamente as propriedades da classe
// Sincronização automática com UI via binder
// ... lógica de autenticação usando this.username e this.password ...
}
}
Referências
- ZK Migration Guide: https://www.zkoss.org/guides/mvvm/migration-guide
- MVC vs MVVM Comparison: https://www.zkoss.org/guides/mvvm/mvvm-vs-mvc
- Best Practices MVVM: https://www.zkoss.org/guides/mvvm/best-practices
- ZK Developer's Reference: https://docs.zkoss.org/zk_dev_ref/
More by KrystianYCSilva
View allContext engineering fundamentals for AI systems: dynamic discovery, compression, caching, memory consolidation, and JIT loading patterns. Use quando: construir agentes AI, otimizar uso de tokens, gerenciar memória conversacional, implementar RAG, carregar contexto incremental.
Implementa o framework CoALA (Cognitive Architectures for Language Agents) com memórias especializadas e ciclo de decisão. Use quando: projetar arquiteturas de agentes de linguagem, implementar sistemas de memória para LLMs, criar agentes com raciocínio multi-etapas, ou estruturar tomada de decisão em agentes autônomos.
Applies Chain-of-Thought (CoT) for step-by-step reasoning in complex problems. Use when: logic, math, multi-step planning, non-native reasoning models like Qwen-Coder, GPT-4o.
Skill para desenvolvimento de aplicações web Java utilizando ZK Framework (Community Edition), seguindo boas práticas de MVC/MVVM e integração com ecossistema Java. Use quando: criar interfaces ZK com componentes ZUML (.zul), implementar padrão MVC ou MVVM, integrar ZK com Spring/JPA/Jersey, migrar entre versões.
