domingo, 28 de novembro de 2010

Usando Vaadin com MVC

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 {
   
    // controllers
    private Controller abrirContaController = new AbrirContaController(new AbrirContaUC());
    private Controller consultarSaldoController = new ConsultarSaldoController(new ConsultarSaldoUC());

   
    // components
    private Window mainWindow = new Window("Vaadin MVC");
    private TabSheet tabs = new TabSheet();
   
    @Override
    public final void init() {
        this.setMainWindow(crieWindow());
    }
   
    public final Window crieWindow() {
        tabs.setSizeFull();
        mainWindow.addComponent(crieAcoes());
        mainWindow.addComponent(tabs);
        ((VerticalLayout) mainWindow.getContent()).setExpandRatio(tabs, 3);
        return mainWindow;
    }

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

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


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

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

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

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) {
              
    // inicia o controller, que retorna uma view
    View view = controller.init(_this()); 
    Component c = view.getComponent();

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

...

package naskar.ui;

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

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

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

}

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 {
   
    // form
    private VerticalLayout form;
   
    // campos
    private TextField campoIdAgencia = new TextField();
    private TextField campoNomeCliente = new TextField();
    private TextField campoRG = new TextField();
   
    // controller
    private AbrirContaController controller;
   
    public AbrirContaView(AbrirContaController controller) {
        this.controller = controller;
        init();
    }
   
    @SuppressWarnings("serial")
    private void init() {
        form = new VerticalLayout();

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

            @Override
            public void buttonClick(ClickEvent event) {
                controller.abraConta(_this());
                controller.getViewManager().showMsg("Conta aberta com sucesso.");
            }

        });
        form.addComponent(new Label(""));
        form.addComponent(btCriarConta);
    }
   
    //
   
    public String getIdAgencia() {
        return (String)campoIdAgencia.getValue();
    }
   
    public String getNomeCliente() {
        return (String)campoNomeCliente.getValue();
    }

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

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

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

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

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.