quinta-feira, 15 de abril de 2010

Flush-mode: Conversation

INTRODUCTION

Jboss Seam is a Java framework for web development which integrates technologies such as Asynchronous JavaScript and XML (AJAX), JavaServer Faces (JSF), Java Persistence (JPA), Enterprise Java Beans (EJB 3.0) and Business Process Management (BPM) into a unified full-stack solution, complete with sophisticated tooling. Its main feature is to easy the use of the technologies it integrates.

I thought about writing a post about jboss seam's main features and why I like it so much, but I won't do it for two reasons:
  1. There are already a lot of material on that topic (here, here, here, here, and here).
  2. The Jboss seam as we know it is coming to its end. The framework is so successful that it was, at least most part of it, incorporated by the JEE6 specification (mainly JSF2 and CDI). The future of the framework is at CDI's portable extensions, integrating the functionalities which were not standardized.



Nevertheless, I believe the actual version is still gonna be used for some time, so its worth to share a few clues related to specific points of the framework. This post in particular is about one of the main features of Jboss Seam: conversations.

CONVERSATION

The concept of conversation was not created by Seam, but it was the seam framework the first one to introduce it as a first class construct. In few words, a conversation is a context to hold application state, which scope is longer than a single request but shorter than the user's http session. A conversation represents a task the user can perform in the application and it is seam's default unit of work.

Under the hood, seam attaches the entity manager to the conversation context, so the entities remain persistent through the unit of work.  Also, it disables the automatic flush of the entity manager  (flush-mode: manual) so the changes don't get propagated to the database prematurely. That responsibility now stands with the developer, which issues a entity manager's flush at the end of the conversation.



The problem with that approach is the potential decrease of business method's re-usability as one method can be the last step of some conversation but not in another. 

AN EXAMPLE

Let's picture an human resources application which domain includes, obviously, employees and departments. It's acceptable to presume that such application includes a CRUD functionality to both models, being possible, in the latter one, to select a chief employee. The employee model includes a flag to determine whether she is a chief or not.

Accepting that a set of business rules are to be applied on a employee update (a conversation), we could come up with the following pseudo-method, responsible for the execution of such rules.

public void editEmployee(Employee employee){

peformRule1();

peformRule2();

peformRule3();

//Synchronize changes in the model with the database at the end of the conversation
this.entityManager.flush(); 


}


On the conversation responsible for editing departments, one of the steps would be exactly to update the chief employee (through the pseudo-method we just saw) and there's where the problem raises because this method executes an unexpected flush before the conversation is over.

public void editDepartamento(Department department, Employee employee){

peformRule1();

peformRule2();

employee.setChiefe(true);

//peforms an unwanted flush
editEmployee(employee); 

peformRule3();

//Synchronize changes in the model with the database at the end of the conversation
this.entityManager.flush(); 


}


For such a simple example, one could suggest some solutions such as force the employee's update to be the last step of the department's editing conversation, or to include a flag parameter to the employee editing method indicating whether it should perform a flush or not. None of these solutions is, nevertheless, adequate. What it's really needed is a way to ensure that the flush will be performed only at the end of a successful conversation, decoupling the database synchronization of the business logic.

//Refactored employee's editing method, receiving a boolean paramether inidicating whether the flush should be executed. Not the best solution!
public void editEmployee(Employee employee, Boolean peformFlush){

peformRule1();

peformRule2();

peformRule3();

if (peformFlush){
this.entityManager.flush(); 
//Synchronize changes in the model with the database at the end of the conversation
}

}

FLUSH MODE CONVERSATION

Even thought it doesn't exists built-in, seam provides mechanisms so we can get the same effect. The following solution is based on seam built-in events, specially one that's rose every time a conversation come to its end. We observe that event, performing a flush when it's needed.

@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();
  }

 }

}

The weak point of such solution appears with the knowledge that not all ended conversations must perform a flush. Particularly, canceled conversations must not or the data will be synchronized with the database anyway. That's why a flag was included, so the component could ask the context whether it should perform the flush or not. It is the programmer's responsibility to feed the context with such boolean variable, whose default value would be true. It could be done, for example, in the actionListener of the cancel button.


@Name("editEmployeeController")
@Scope(ScopeType.CONVERSATION)
public class EditEmployeeController{

 @Out
 private Boolean flushEntityManager;

 @In
 private EntityManager entityManager;

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

 }

}

CONCLUSION

In this post we talked about seam's conversations and how they are usually implemented. An alternative approach was proposed, seeking to increase business methods reusability. Such approach uses bult-in mechanisms to decouple the database synchronization of the business methods.  

Nenhum comentário:

Postar um comentário