Usando Vaadin com MVC
Autor: Rafael | Publicado: 2010-11-28T03:48:00-03:00

Como utilizar o Vaadin em larga escala, fazendo que o seu código fique limpo e sem regras de negócio na camada de interface com o usuário, o assim chamado Smart UI em Domain-Drive Design. 

Nesse post veremos como utilizar o padrão MVC de forma simples numa arquitetura onde separa a camada de apresentação da camada de negócio, que representaremos aqui como classes de casos de uso.





MVC

Relembrando o padrão MVC, onde o usuário inicia um evento, que é recebido por um Controller, este acessa um certo Model e atualiza os dados de um ou mais Views, podemos ter o seguinte fluxo abaixo:






fonte: http://best-practice-software-engineering.ifs.tuwien.ac.at/patterns/mvc.html


Caso de Uso 

Vamos analisar o fluxo do caso de uso Abrir Conta Bancária, onde ao clicar no botão Abrir Conta, é apresentada a tela abaixo e o usuário preenche Agência, Nome do Cliente e RG, e o sistema cria uma conta bancária. Abaixo veja o screenshot da tela final:




Abaixo segue a sequência de passos:










Classes

Veja, que a aplicação inicia quando o usuário acessa o site, onde é criada uma instância da classe Main, que é uma Application do Vaadin. Veja o código abaixo:

package naskar.ui;

import naskar.application.AbrirContaUC;
import naskar.application.ConsultarSaldoUC;
import naskar.infrastructure.Controller;
import naskar.infrastructure.View;
import naskar.infrastructure.ViewManager;
import naskar.infrastructure.WindowUtils;

import com.vaadin.Application;
import com.vaadin.ui.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.TabSheet.Tab;

@SuppressWarnings("serial")
public class Main extends Application implements ViewManager {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // controllers
&nbsp;&nbsp;&nbsp; private Controller abrirContaController = new AbrirContaController(new AbrirContaUC());&nbsp;&nbsp;&nbsp; private Controller consultarSaldoController = new ConsultarSaldoController(new ConsultarSaldoUC());
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // components
&nbsp;&nbsp;&nbsp; private Window mainWindow = new Window("Vaadin MVC");
&nbsp;&nbsp;&nbsp; private TabSheet tabs = new TabSheet();
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public final void init() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.setMainWindow(crieWindow());
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public final Window crieWindow() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; tabs.setSizeFull();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; mainWindow.addComponent(crieAcoes());
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; mainWindow.addComponent(tabs);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ((VerticalLayout) mainWindow.getContent()).setExpandRatio(tabs, 3);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return mainWindow;
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; private Component crieAcoes() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; HorizontalLayout hl = new HorizontalLayout();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; hl.addComponent(crieAcao("Abrir Conta", abrirContaController));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; hl.addComponent(crieAcao("Consultar Saldo", consultarSaldoController));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return hl;
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; private Component crieAcao(final String nome, final Controller controller) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // cada acao sera um botao
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Button b = new Button(nome);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; b.addListener(new ClickListener() {

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public void buttonClick(ClickEvent event) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // inicia o controller, que retorna uma view&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; View view = controller.init(_this());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Component c = view.getComponent();

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // ao clicar ele irá criar uma nova tab
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Tab t = tabs.addTab(c, nome, null);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; t.setClosable(true);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; tabs.setSelectedTab(c);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; });
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return b;
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public Main _this() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return this;
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // ViewManager
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public void close(View view) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // remove a view da TabSheet
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; tabs.removeComponent(view.getComponent());
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public void showMsg(String msg) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; WindowUtils.showMsg(mainWindow, msg);
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public void showError(String error) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; WindowUtils.showError(mainWindow, error);
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public Window getWindow() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return mainWindow;
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
}

Na classe Main, são criados os Controllers e os Casos de Uso, e é montado a Window com os respectivos botões para cada Controller.(Você também integrar sua camada de negócio usando o Spring)

Após isso, o usuário clica no botão Abrir Conta, onde o objeto da classe Main aciona AbrirContaController, que cria e retorna uma instância de AbrirContaView. É nesse momento que o Controller pode acessar a camada de negócio para obter dados para passar para a View, como listas de entidades para Grids, ComboBox, etc


@Override

public void buttonClick(ClickEvent event) {
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // inicia o controller, que retorna uma view
&nbsp;&nbsp;&nbsp; View view = controller.init(_this());&nbsp;

&nbsp;&nbsp;&nbsp; Component c = view.getComponent();

&nbsp;&nbsp;&nbsp; // ao clicar ele irá criar uma nova tab
&nbsp;&nbsp;&nbsp; Tab t = tabs.addTab(c, nome, null);
&nbsp;&nbsp;&nbsp; t.setClosable(true);
&nbsp;&nbsp;&nbsp; tabs.setSelectedTab(c);

}


... 

package naskar.ui;

import naskar.application.AbrirContaUC;
import naskar.infrastructure.Controller;
import naskar.infrastructure.ViewManager;

public class AbrirContaController extends Controller {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // use case
&nbsp;&nbsp;&nbsp; private AbrirContaUC uc;
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public AbrirContaController(AbrirContaUC uc) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.uc = uc;
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // init 
&nbsp;&nbsp;&nbsp; public AbrirContaView init(ViewManager viewManager) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; setViewManager(viewManager);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return new AbrirContaView(this);
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; // uma acao do usuario
&nbsp;&nbsp;&nbsp; public void abraConta(AbrirContaView view) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //TODO: regras de telas
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; uc.abraConta(new Long(view.getIdAgencia()), view.getNomeCliente(), view.getRG());
&nbsp;&nbsp;&nbsp; }

}

Na View, teremos os componentes de interface com usuário sendo criados e os eventos sendo disparados para o Controller, que irá ter as regras de tela necessárias e irá chamar os casos de uso, que terão as regras de negócio.

package naskar.ui;

import naskar.infrastructure.View;

import com.vaadin.ui.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;

public class AbrirContaView implements View {
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // form
&nbsp;&nbsp;&nbsp; private VerticalLayout form;
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // campos
&nbsp;&nbsp;&nbsp; private TextField campoIdAgencia = new TextField();
&nbsp;&nbsp;&nbsp; private TextField campoNomeCliente = new TextField();
&nbsp;&nbsp;&nbsp; private TextField campoRG = new TextField();
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // controller
&nbsp;&nbsp;&nbsp; private AbrirContaController controller;
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public AbrirContaView(AbrirContaController controller) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.controller = controller;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; init();
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; @SuppressWarnings("serial")
&nbsp;&nbsp;&nbsp; private void init() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; form = new VerticalLayout();

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; form.addComponent(new Label("Agência: "));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; form.addComponent(campoIdAgencia);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; form.addComponent(new Label("Nome Cliente: "));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; form.addComponent(campoNomeCliente);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; form.addComponent(new Label("RG: "));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; form.addComponent(campoRG);
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Button btCriarConta = new Button("Efetuar Abertura de Conta");
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; btCriarConta.addListener(new ClickListener() {

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; @Override&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public void buttonClick(ClickEvent event) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; controller.abraConta(_this());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; controller.getViewManager().showMsg("Conta aberta com sucesso.");&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; });
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; form.addComponent(new Label(""));
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; form.addComponent(btCriarConta);
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; // 
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public String getIdAgencia() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return (String)campoIdAgencia.getValue();
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public String getNomeCliente() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return (String)campoNomeCliente.getValue();
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; public String getRG() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return (String)campoRG.getValue();
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; //
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; public void setIdAgencia(String idAgencia) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.campoIdAgencia.setValue(idAgencia);
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; public void setNomeCliente(String nomeCliente) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.campoNomeCliente.setValue(nomeCliente);
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; public void setRG(String campoRG) {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.campoRG.setValue(campoRG);
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; //
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; private AbrirContaView _this() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return this;
&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; // view
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; @Override
&nbsp;&nbsp;&nbsp; public Component getComponent() {
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return form;
&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; 
}

Veja que para cada Controller, sempre teremos um ou mais Views, ou seja, Controller e Views são intimamente relacionados:



fonte: http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html



Conclusão

A "mágica" do MVC é a separação dos componentes de interface com usuário de quem trata os eventos gerados pelo usuário. Devido o Vaadin ser muito fácil de usar, pode acontecer de suas classes "incharem" e ficarem com grandes responsabilidades. O MVC vai te ajudar a separar essas responsabilidades e deixar o código mais claro.

Veja também os padrões MVP e PAC, que relatam também desse tipo de separação da apresentação das regras de tela e de negócio.

Você pode baixar aqui essa aplicação de exemplo onde contém os casos de uso Abrir Conta e Consultar Saldo implementados.