domingo, 11 de abril de 2010

flush-mode: Conversação

INTRODUÇÃO

Jboss Seam é um framework Java focado no desenvolvimento web que integra tecnologias como Ajax, Java Server Faces (JSF), Java Persistence (JPA), Enterprise Java Bean (EJB) e Business Process Management (BPM) em uma única solução apoiada por uma série de ferramentas sofisticadas. Sua principal característica é facilitar o uso das tecnologias que integra.

Pensei em fazer um post descrevendo as principais características deste framework e porque eu gosto tanto dele, mas não vou fazê-lo por 2 motivos:
  1. Já existe muito material assim por ai (aqui, aqui,aqui,aqui e aqui).
  2. O Jboss seam como existe hoje está com os dias contados. O framework foi tão bem sucedido que acabou sendo, em sua maioria, absorvido por especificações da plataforma JEE6 (em especial CDI e JSF2) O futuro do framework está em extensões portáveis para CDI integrando as funcionalidades não incorporadas a especificação.
No entanto, acredito que a versão atual ainda será usada por algum tempo, portanto, vale compartilhar algumas dicas referentes a pontos específicos do framework. A dica deste post é sobre uma das características mais marcantes do framework: conversações.

CONVERSAÇÃO

O conceito de conversação não foi inventado pelo jboss seam, mas foi este framework o primeiro a incorporá-lo como construção de primeira classe. Resumidamente pode-se dizer que uma conversação é um contexto onde o estado da aplicação pode ser armazenado, cujo escopo é maior que uma requisição mas inferior a sessão do usuário. Uma conversação representa uma tarefa que o usuário pode realizar na aplicação e é a unidade de trabalho padrão no jboss seam.

Por baixo dos panos, o seam inclui o entity manager no escopo de conversação, de forma que as entidades permaneçam persistentes durante toda a unidade de trabalho e desabilita o flush automático, (Flush-Mode: Manual) de forma que as alterações realizadas não sejam propagadas para o banco prematuramente. Esta responsabilidade é repassada ao desenvolvedor, que deve executar manualmente o flush no fim da conversação.



O problema com esta abordagem é que potencialmente pode reduzir a reusabilidade dos métodos de negócio, que em determinados momentos podem ser o último passo da conversação e em outros não.

UM EXEMPLO

Imaginemos por exemplo uma aplicação de RH cujo domínio inclui, obviamente, colaboradores e departamentos. É aceitável presumir que tal aplicação inclua, entre outras, uma funcionalidade de edição de colabores e outra para edição de departamentos, sendo possível, nesta última, apontar o colaborador responsável pelo departamento. A entidade que representa os colaboradores possui um marcador indicando determinado colaborador é um chefe ou não.

Se aceitarmos que uma série de regras de negócio deve ser executada durante a edição de um colaborador (uma conversação), podemos imaginar o seguinte pseudo-método, responsável pela execução destas regras.


public void editarColaborador(Colaborador colaborador){

executarRegra1();

executarRegra2();

executarRegra3();

//sincroniza as alterações na model com a base de dados no fim da conversação
this.entityManager.flush(); 


}


Na conversação referente à edição de departamentos, um dos passos seria justamente a edição do colaborador. O método negocial previamente definido deverá, portanto, ser invocado, e é ai que surge o problema porque este método executa o flush do entityManager o que é desejável apenas no fim da conversação da edição de departamentos.

public void editarDepartamento(Departamento departamento, Colaborador colaborador){

executarRegra1();

executarRegra2();

colaborador.setChefe(true);

//executa flush inadvertidamente
editarColaborador(colaborador); 

executarRegra3();

//sincroniza as alterações na model com a base de dados no fim da conversação
this.entityManager.flush(); 


}


Para este caso simples, algumas soluções podem ser propostas, como forçar que a edição do colaborador seja o último passo da conversação de edição de departamentos ou a inclusão de um parâmetro boleano no método de edição de colaborador indicando se o flush deve ou não acontecer. Nenhuma destas soluções, entretanto, é a adequada: o que precisamos mesmo é assegurar que o flush irá ocorrer apenas no fim de uma conversação bem sucedida, desacoplando a sincronização da lógica nos métodos negociais.


//Método de edição de colaborador refatorado para receber um parâmetro boleano indicando se o flush deve ou não ser executado. Não é a melhor solução!
public void editarColaborador(Colaborador colaborador, Boolean executarFlush){

executarRegra1();

executarRegra2();

executarRegra3();

if (executarFlush){
this.entityManager.flush(); //sincroniza as alterações na model com a base de dados
}

}

FLUSH-MODE: CONVERSATION

Apesar de não existir de forma built-in, o jboss seam disponibiliza alguns mecanismos que permitem obter efeito semelhante. A solução a seguir baseia-se no fato do seam levantar alguns eventos por padrão, entre eles um que indica o fim de uma conversação. A Idéia, portanto, consiste em disponibilizar um componente que observa este evento, fazendo o flush quando necessário.

@Name("endConverationObserver")
@Scope(ScopeType.CONVERSATION)
public class EndConverationObserver {

 @In
 private Boolean flushEntityManager;

 @In
 private EntityManager entityManager;

 @Observer(value = "org.jboss.seam.endConversation", create = true)
 public void DetermineflushEntityManager() {
  if (flushEntityManager) {
   this.entityManager.flush();
  }

 }

}


O ponto fraco desta solução emerge na percepção de que nem todo fim de conversação deve incluir sincronização com o banco. Em especial, as conversações canceladas não devem ter seus dados sincronizados! Para isso, foi incluída uma flag que indica se a sincronização deve mesmo ser efetuada, com o valor padrão igual a true. Se a sincronização não for desejada, o usuário deverá indicar isso setando o valor da variável para false. Isso pode ser feito por manipulação direta do contexto ou por meio de uma variável a ser ejetada.

@Name("editColaboradorController")
@Scope(ScopeType.CONVERSATION)
public class EditColaboradorController{

 @Out
 private Boolean flushEntityManager;

 @In
 private EntityManager entityManager;

 public String cancel() {
  this.flushEntityManager = false;
                return "editCancel"

 }

}

CONCLUSÃO

Neste post falamos sobre conversações no jboss seam e da maneira como geralmente são implementadas. Uma abordagem alternativa foi sugerida, buscando manter a reusabilidade dos métodos de negócio entre as conversações. Tal abordagem faz uso de recursos do próprio framework, como eventos bult-in e variáveis de contexto, para desacoplar a sincronização dos dados com o banco das regras de negócio.

Um comentário: