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
...
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
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.
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());
// 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);
// 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.