This blog is mainly about Java...

Thursday, October 7, 2010

Why you should NOT use the Seam Application Framework

NOT TO BE CONFUSED WITH SEAM, WHICH IS AWESOME!

I am a very experienced Seam user. I have used Seam daily now for almost 3 years.

In this blog post, I will try to convince you to avoid the Seam Application Framework, and truly understanding when it should be used, and when it shouldn't.

What is Seam Application Framework?
 Seam in Action says: "Seam is an application framework for Java EE. It provides a container that manages components and leverage's those components to tie the layers of an enterprise Java application together. Nestled within the Seam code base lies a handful of classes that comprise the Seam Application Framework. This "framework within a framework" is a specialized collection of component templates that effortlessly blanket the programming requirements of garden-variety web applications. Such tasks include performing create,read,update and delete (CRUD) operations on entity instances;querying for data; and developing JSF page controllers. You may be hesitant to give this spattering of classes much notice, but I assure you that they'll carry you a long way."

According to Dan Allen, he is encouraging you to check this "framework within a framework" out. However, though he is right, that there are benefits with this framework, I will prove that there are far more disadvantages than advantages and that he should have rewritten the last line to include that the framework is only really useful for small CRUD applications, and not something to leverage and build upon.

Way too many people are using this framework
If you have a look at the Seamframework.org forums, you will see tons of questions from people having problems and issues with the Seam Application Framework. Either a lot of people are developing small CRUD based applications, or there is a growing misconception about Seam and Seam Application Framework.

There are many people that are confused about this notion. They think that Seam == Seam Application Framework, and that they have to use it. And who can blame them? The seam-gen utility is a fantastic way of getting you quickly started. However, I have always encouraged people to only use the create-project and generate-entities part of the utility, and not the generate-ui part of it, this because most people I have talked with, are not developing small CRUD based applications, but rather medium sized enterprise apps, but since you get the Seam Application Framework for free, it is quite easy to extend and use it.

PROS
I did mention that there are a few pro's about using this framework, but it depends on people truly understanding the inner workings of the framework.
  • Quick to get started
  • Excellent for small models,presentation and proof of concept
  • Great in-built pagination of lists
  • Can @Override the methods and inherit classes to extend/tweak upon the framework
If you want to visualize for your customer, boss or client, an application as a proof of concept, then it is very easy to do this using the framework. In this case you don't care so much about performance and inherited abstraction layers, but rather how quickly you can get something visual and up and running.

I also want to note, before I get spammed with comments about how wrong I am, that you can if you know what you are doing, speed things up. Details can be found here. However, this again requires some knowledge about Seam.

CONS
  • Its a bit slower
  • More abstraction which might not suit your requirements
  • More difficult to use than the plain components. You need to "learn a new framework as well as Seam"

Consider this very very simple example. We have a SystemLog, and we want to display the log.


@Entity
public class SystemLog {

    @Getter @Setter
    @Id
    @GeneratedValue
    private Long id;

    @Getter @Setter
    @NotEmpty
    private String description;

    @Getter @Setter
    @Column
    private String category;

    @Getter @Setter
    @Temporal(TemporalType.TIMESTAMP)
    private Date date;

    @Getter @Setter
    @Column
    private String username;

    @Getter @Setter
    @Column
    private String organization;

    @Getter @Setter
    @Column(length = 1024)
    @Lob
    private String details;

    @Getter @Setter
    @Column
    private String severity;

    @Getter @Setter
    @Column
    private String ipAddress;

}

We have an action class that used the EntityQuery of the Seam Application Framework.


@Name("systemLogQuery")
@MeasureCalls
public class SystemLogQuery extends EntityQuery<SystemLog> {
  @Logger Log log;
  
  @Override
  public String getEjbql() {
    return "select sys from SystemLog sys";
  }
  
  @Override
  public List<SystemLog> getResultList() {
    log.warn("Inside systemlog query getResultList");
    return super.getResultList();
  }

}

And this normal (non tweaked) seam component.


@Name("systemLogNormal")
@MeasureCalls
public class SystemLogNormal {

  @Logger Log log;
  
  @In EntityManager entityManager;
  
  List<SystemLog> result;
  
  public List<SystemLog> getResultList() {
    log.warn("Inside systemLog normal resultList");
    if(result == null)
      this.result = entityManager.createQuery("FROM SystemLog").getResultList();
     
    return result;
  }
}


And a simple data table iterating over the elements

<h:dataTable value="#{systemLogNormal.resultList}" var="sys">
    <h:column>
      <f:facet name="header">
        description
      </f:facet>
      #{sys.description}
    </h:column>
</h:dataTable>

Now at first glance, I think the normal seam component is much cleaner and easier to read. Remember, this is a very simple example. In a normal real life example you would have extended the component to be much more complicated, and if you want to do this using the existing framework, you will need to find out what the framework already is doing, and correctly override those methods. Otherwise, you risk of doing the same thing twice.

If you ask your self what the @MeasureCalls annotation is doing, then have a look here, and scroll down to the second example. But a short description is that it measures the time of execution for a method.

When you run this example using the SystemLogNormal and the Query component, they are pretty much equal. However, you will see that the Query component is also calling the validate() method, which takes a few milliseconds to execute.
It is correct that a few milliseconds is hardly anything noteworthy, however this is a very very simple example, with very little data, and in a more realistic scenario, the number could possibly be different. Either way, the overall time difference is that the EntityQuery object is a few milliseconds slower.

13:58:24,224 WARN  [SystemLogQuery] Inside systemlog query getResultList
13:58:24,228 INFO  [STDOUT] Hibernate: 
    select
        systemlog0_.id as id0_,
        systemlog0_.category as category0_,
        systemlog0_.date as date0_,
        systemlog0_.description as descript4_0_,
        systemlog0_.details as details0_,
        systemlog0_.ipAddress as ipAddress0_,
        systemlog0_.organization as organiza7_0_,
        systemlog0_.severity as severity0_,
        systemlog0_.username as username0_ 
    from
        SystemLog systemlog0_
13:58:24,239 WARN  [SystemLogQuery] Inside systemlog query getResultList
13:58:24,257 WARN  [SystemLogQuery] Inside systemlog query getResultList
13:58:24,334 INFO  [TimingFilter] 2.201607 ms   1   EntityQuery.validate()
13:58:24,334 INFO  [TimingFilter] 15.967185 ms   3   SystemLogQuery.getResultList()



13:58:31,647 WARN  [SystemLogNormal] Inside systemLog normal resultList
13:58:31,648 INFO  [STDOUT] Hibernate: 
    select
        systemlog0_.id as id0_,
        systemlog0_.category as category0_,
        systemlog0_.date as date0_,
        systemlog0_.description as descript4_0_,
        systemlog0_.details as details0_,
        systemlog0_.ipAddress as ipAddress0_,
        systemlog0_.organization as organiza7_0_,
        systemlog0_.severity as severity0_,
        systemlog0_.username as username0_ 
    from
        SystemLog systemlog0_
13:58:31,662 WARN  [SystemLogNormal] Inside systemLog normal resultList
13:58:31,678 WARN  [SystemLogNormal] Inside systemLog normal resultList
13:58:31,712 INFO  [TimingFilter] 15.740409 ms   3   SystemLogNormal.getResultList()


As you can see, we get three outputs of "Inside systemlog". Really there should only be one. So adding a @Factory annotation on top of it will do the trick for both versions.

Then there are the Home and Query objects, with methods like isIdDefined(), clearDirty(), isManaged(), getEjbql(), RESTRICTIONS and more confusing methods which you need to read the documentation to understand.

Conclusion

In my opinion, for first time users who are learning Seam, it is more than enough to understand and learn the framework, instead of in addition learning "a framework inside a framework". The seam-gen utility is sort of a devil in disguise. Its great for a shortcut to get started, but its evil in disguise for new comers.

Newbies think Seam Application Framework is Seam, and all components they create must extends the Home, List or Query objects. Its confusing, and in my opinion they should avoid using it unless they know all the ins and out of both Seam and the application framework.

So if you are new to Seam, start by looking at the Seam examples that comes with the bundle. They are far better to use, than generating the UI with seam-gen and going from there.

Labels