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:
- Já existe muito material assim por ai (aqui, aqui,aqui,aqui e aqui).
- 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.
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.
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.
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.
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.
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.
Muito bom, parabéns !!!
ResponderExcluir