Table of Contents
First off, these are two excellent solutions to have to choose between. Most of the problems with basic CRUD web development can be solved with either of these frameworks, and it's fairly safe to say that there are no real reasons not to use either of these.
Let's start by recapping some of the differences between the two and noting their respective strengths and weaknesses in different areas
Seam comes with Seam-Gen which is a command line
tool to get you started writing Seam applications.
Alternatively, you can use the JBoss Tools / JBoss
Developer Studio tools to create applications. Seam
requires you to list any page flows in the
components.xml
file. Annotations are usually used to name
components in source, but that too can be done in
components.xml
. Pageflows are invoked by name when a conversation
is started.
Spring essentially leaves application creation up to the developer which isn't surprising given that Spring gives you unlimited options on the libraries you can use with it. Web Flow configuration involves a number of different classes and beans. It also requires you to be running Spring MVC underneath it all. It wouldn't be a Spring solution if you couldn't interchange nearly each and every part of the solution. For that reason, I'm sure there are no limits on how you could set up the mappings between URLs and flows. The default is to map URLs to flows with the option of using wildcards. Spring beans are defined either by annotation or using xml configuration files. These can be imported into the spring config files so there is no rigid structure in terms of content or location.
In addition to the stateless, session and application scopes, both frameworks offer additional scopes. Spring offers a flash, view, flow or conversation scope, with the conversation spanning one or more flows. Seam lets you define a conversation scope which is equivalent to SWF's flow scope, or a page scope which holds the object in the page and appears similar to Spring's view scope. As a modifier to the Seam conversation scope, you can indicate that an object is created per nested conversation giving it a scope limited to that conversation, similar to Spring's flow scope.
Spring lets you wrap values in a JSF
DataModel
which can be specified in the flow. Seam also
enables you to easily do so using an annotation
which requires that you have an actual class to
annotate. If you define a query in
components.xml
then there is no way to wrap the results in a
DataModel
.
Regarding instance declaration, Spring uses a 'push' convention over 'pull' while Seam can use both although it tends towards the pull convention. With Spring, you need to declare a data instance in the flow before you use it, while with Seam, you typically declare how that data is created with a factory annotation before you can use it. When it is requested (usually from a reference on a JSF page), the data instance is created and put into one of the Seam variable contexts (depending on the assigned scope). Alternatively, you can just outject object instances from classes to emulate a push technique, although this technique requires you to call a method call on the bean to trigger the outjection process.
Seam uses a global approach to defining it's named
beans which gives you the benefit of being able to
define beans once and use them anywhere in the
application. Seam also allows you to create multiple
instances of the same class with different names and
possibly different scopes. Defining a component with
perNestedConversation
gives you most of the power of Spring's declaration
within the flow. However, this cannot easily be
applied to the persistence context which by default,
only allows one instance per conversation stack.
Since Spring defines the data in the flow itself, it gives you slightly more control on a per flow basis, but it could lead to repetition and the possibility of variable names stepping in each others toes as one flow invokes another flow. However, this could be managed by scope control of the variables created and mostly using the more restrictive flow scope, and only using conversational scope when needed. Flow inheritance could solve the repetition issue, but it might be overkill to create a flow with common code in just to be inherited by two flows (i.e. edit and view CRUD pages).
Seam offers natural conversation urls where the
conversation is identified by a an identifier that
is contextual. By default, a Seam url with a
conversation is
partEdit.seam?partId=1234?cid=5
where the conversation is identified by the
cid
parameter. You can define a naturial conversation
for that page that makes the conversation Id defined
by the part number parameter instead so rather than
have a synthetic conversation id of 5 passed in a
parameter called
cid
, you will have a contextual conversation id defined
by the part number in the
partId
parameter. This leaves you with a URL of
/partEdit.seam?partId=1234
, or, if you use the UrlRewriter a RESTful URL of
/parts/edit/1234
. Spring appends something like
?execution=e3s1
on to your URLs which can be quite ugly. The '
s
' part of the URL indicates the state and increments
for each page in the flow. It is this that
identifies each state point in the flow allowing us
to use the back button to get to any point in the
flow and essentially, travel back in time to that
point, and resume the flow from that point. Seam
just has one conversation that spans all pages that
participate in it and only one set of state which is
the current state.
Both solutions provided an easy way to write navigation rules and flows, and took very different approaches to it.
Spring navigation is more decoupled from the view
than Seam. Spring favors returning action strings
like
save
,
cancel
, or
select
from the view and executing code within the flow
based on those actions. It is probably the more
technically correct way to do it from the sense of
decoupling the view from the navigation and business
logic. While the existing navigation language is
simple, but fairly adequate, the Web Flow team has
talked about adding new features based upon user
feedback. On important feature I found was the
ability to put as many commands as you wanted in the
transition segments.
Seam gives developers a choice of navigation
methods. While you can use the JSF based navigation,
it is much easier to use one of the two
alternatives. If you use
pages.xml
for navigation, you navigate based on string
actions, on logical statements, and it also depends
on what method the string action was returned from.
However, you cannot call methods in response to
those string actions as part of the navigation.
Typically this means your view has to call a method
to perform an action and you navigate based on the
outcome of that action. In order to use string
actions in your view and call methods on the back
end, you must use page flows like Spring Web Flow
does. Having multiple navigation methodologies might
seem overkill and complex, but it does offer
solutions of different degrees. The page flow
language does feel rigid, and unforgiving in many
ways. There's no way to make multiple method calls,
there's no way to outject values to the parent
conversation. It often feels like you are digging
for ways to do things which should be intuitive.
Another issue is that if you navigate through the
flow, and click the back button and try to take
another path, you get Illegal Navigation error
messages. This is because the flow isn't
synchronized with the view. If you navigate from
page A to page B, and click the back button and try
to navigate off page A using action string "XYZ",
the pageflow handler is still on page B, and since
page B doesn't know about action string "ABC", it
throws an illegal navigation error. Spring has a
finer granularity in storing its state. When it
appends the execution info in the url, it is of the
form
execution=e3s4
. When you navigate through the flow the number
after the 's' increments, so Spring stores not only
the state for the flow, but also for each state of
the flow on a page by page basis. This is handy for
avoiding problems like the one shown above, but can
come with it's own set of headaches. Spring however
lets us discard or invalidate history on a
transition by transition basis.
One thing that should be noted is that it appears that Spring Web Flow cannot be used for pages not included in a web flow. If you have a page that is not in a page flow and you want to display some data, then you still need to provide some way to generate that data and bind it to the EL expression for JSF to display. For this reason, any time you want to use Web Flow for passing data to JSF, it must be done within a flow. While there is a JSF variable resolver for Spring, it means you have to learn and use a whole different technique for implementing JSF pages with Spring. On the other hand, you could also use this as an opportunity to delve in to some Spring MVC for those pages since you already have most if not all Spring MVC elements installed in your application.
Seam on the other hand is designed to be used with
varying degrees of intrusion into your pages. You
don't have to start a conversation and a pageflow in
order to use the Seam defined variables. The
expression
#{projects}
works in any page and even outside of pages because
the declaration is application wide. This can even
make your java code conversational since it always
executes within a conversation. EL expression values
can be used for error messages, navigation rules, in
any email or PDFs you generate and even logging.
Overall, Seam delivers a more unified and integrated
approach to fetching and generating data than
Spring.
One point where Spring shines is the ability to
localize the data in a flow resulting in cleaner
nested flows. With Seam, data is declared globally,
there is the
@PerNestedConversation
annotation that can let you define new instances of
variables in a nested conversation which is a half
measure to achieving the same functionality.
However, passing data to a nested or parent
conversation is not easily done. Part of this
probably stems from Seam's perception of nested
conversations. To Seam, a nested conversation is
meant to be a part of the parent conversation, not a
separate action. I.e. choosing a hotel room type in
the nested flow for a hotel booking that is in the
parent flow. Spring on the other hand can let you
use subflows as totally separate and encapsulated
processes such as editing an issue, going to select
a project for the issue, and then choosing to add a
new project). This behavior is very thick-client-ish
since it gives the users a lot of flexibility.
However, in Seam, as soon as you go to create a new
project, it would look up the
project
variable and find the instance from the parent
conversation, thus preventing you from adding a new
one. You would then have to add the
PerNestedConversation
annotation to the projects bean which could
introduce problems of its own. While this is very
potential problem, it isn't a big one. Interfaces
that let users go round in endless circles usually
end up with the user getting lost and to some degree
should be somewhat constrained. However, it was
impressive going round in circles in the Spring
implementation and having Spring keep track of my
flows and data instances and shuffling up and down
the conversation stack with ease.
Seam has excellent documentation, and a large number
of Samples, with a 650+ page PDF manual starting
with simple examples and documenting most of the
features with a mini example of the syntax for each.
The documentation for core Spring is excellent,
however the documentation for Web Flow is lagging
behind with most functions having little coverage.
This is understandable to a degree, and Seam
documentation was lacking (although a little better
than SWF's current state) for the first few releases
until Gavin took the time to document and it grew
from 250 pages in 1.2 to 350 pages in version 2.0.1
to 650+ in 2.1.0. However, poor documentation can be
annoying, especially after I wasted a couple of
hours trying to get the expression
currentEvent.entity
to work when changes in the web flow API made in May
2008 required it to be
currentEvent.attributes.entity
. The documentation, as of January 2009 still
reflects the old version. It wasn't until I read the
release notes that I found out about the change.
Spring has only a few examples, mainly a duplication
of the Seam booking application, with one version
for each of the different web flow technologies.
Since many people use the examples as a basis for
finding solutions to their own problems, fewer
examples means less chance that people will find
answers within them.
It might be unfair to compare how far these two products go to provide a complete solution since Spring isn't aiming to do that. Spring offers page flow control and stateful data management on top of its IoC container and other core functions. It doesn't strive to be a complete solution, and it expects you to add in any additional nuts and bolts (i.e. security, Ajax Frameworks, iText or email generation) yourself, and either implement your own Spring integration with third party libraries or hope the libraries already have it built in. Either way adding the pieces to the Spring stack would take time and the integration probably won't be as seamless.
Seam on the other hand aims to be a complete software stack. Cynics might disagree with the idea of someone else choosing which libraries they use for development since in this day and age it almost feels like Framework and library selection should be a full time job for Java developers. However, the Seam team has strived to create a stack that really does deliver almost everything a developer could need. Most components could be replaced if needed, but out of the box, everything works together nicely. If you want to get going and start writing an application with Seam and the JBoss Tools IDE, you can find yourself very productive in less than a minute, complete with IDE integration and hot deploy.
Both frameworks let you design layered applications in order to separate concerns. They both let you create Dao or Service beans which provide generic services to more function specific beans. Seam has come under some fire due to the lack of apparent layering in the demo applications. However, the lack of layering is by design in the demos (they don't really need it), and not because Seam is incapable of layering. You can add as much layering as your sanity will allow under Seam without a problem.
One advantage of JSF is the ability to decouple the
view from the back end objects via the use of EL to
loosely couple beans to view elements. Seam expands
on this with contextually named data that results in
less coupling between the view and the data in the
conversation. If we use the expression
#{customers}
in several pages, our view is only bound to this
expression. Seam then binds this expression to a
method or object using a factory. At a later date,
we can change the factory for the
customers
expression to another method, or another bean, or
even another layer in our applications and all
bindings in our view will get the data from our new
source.
With Spring, because all of our variable
declarations are in the flows themselves, if we want
to fetch our customers from a different location, we
need to change the declaration in each flow that
uses it. This is because each flow couples that
variable name to a method. Spring can eliminate the
problem by layering the application and having a
customerDao
with a
getCustomers
method which is then coupled to the expression
customers
in the flow. To change the source of the data, you
need change the implementation in the Dao. This
would require a little pre-thought before
implementation. However, it does demonstrate one
reason why Seam doesn't require as much layering as
it has an extra layer (Seam itself) between the view
and the backing beans.
Another aspect of this is code portability. Spring
maintains the same Model/Dao layering where the Dao
returns model data, and model data is put into the
flow under a variable name which makes it accessible
to the view. Spring makes it fairly easy to take
that Model/Dao code and use it in another
application or test environment, as well as keeping
things lightweight. Seam on other hand promotes
thicker classes in the conversation. For example,
the
EntityHome
beans contains the reference to the model entity
instance, as well as all the Dao functionality
including a reference to the
EntityManager
. While this is still somewhat portable, there is a
higher degree of integration of the code with Seam
than with Spring's 'Roll Your Own' data access
approach. You can of course roll your own data
access layer with Seam, but then you lose the ease
of using the EntityHome/Query beans.
Of course, one factor in all of this is how well the frameworks will weather the future. Nobody wants to adopt a framework that will be extinct in a couple of years. Spring has somewhat of an advantage here in that there are plenty of Spring users already in existence, although few of those are Web Flow users. One might compare that to the number of EJB users versus the number of Seam users since there are plenty of people use EJB but without Seam.
Gavin King is working on creating the Web Beans JSR (JSR-299) which will seek to make a standard out of the component declaration model used in Seam and Google Guice. This is not a case of making Seam a standard since I believe there are a number of differences between Seam and Web Beans. However, once Web Beans is out, Seam will probably move towards being a Web Beans implementation and is probably due to become the reference implementation for the JSR. Depite building the framework around standards, there is support for Wicket, Adobe Flex and GWT. A web beans standard can likely help those integrations by standardizing the use of contextual named components. Web beans will also become a more pojo oriented framework which could attract those that run screaming at the sight of EJBs. However, since Web Beans is a standard, like all standards, it will not be as complete as either Seam or Spring as a solution. One concern for Seam could be that once Web Beans comes out, will we start seeing Web Beans based stateful frameworks without all the additional features that Seam has (pageflows, security, deep EL integration etc) that might be more attractive as Seam-Lite.
Spring have been moving ahead with their own efforts to create a single full stack with the Spring Application Platform focused around an OSGI based application server. They are passing up on the standards and sticking with their own solutions. Interestingly enough Spring are pushing their own application servers for use with their own Spring stack, while the Seam team are making efforts to promote cross server usage of Seam.
While many have aversions to standards (rational and otherwise), there is a varying degree of benefit to adopting standard technologies. Seam is all about standards, and will probably continue to be a framework built on standards. Spring on the other hand caters to the standards when they think it is prudent (i.e. plenty of JSF integration with Spring Web Flow), and ignore them when they think it is not. Support for Spring Web Flow with other frameworks is more likely to come from the other frameworks trying to integrate with Spring rather than the Spring Team reaching out to other frameworks. The Seam team has made a number of efforts to include other frameworks (i.e. Wicket, GWT and even spring), as well as other app services (WebSphere OC4j, Glassfish).
Sometimes, SWF feels like a more professional and polished product with professional developers building real world products in mind. As such it seems not to suffer from some of the pains that Seam has, many of which can be overcome, but some that require a little work. Even when things go wrong in Spring, you get a fairly clear error message most of the time (but not always!), sometimes even a suggestion on how to fix it. With Seam, you often have to first decode the exception to determine where things went wrong, and then fix the application. For example, if you have an error in your pageflow, it will often loop endlessly complaining that the workflow hasn't started. Somewhere in there (or at the top of the log) is a helpful error message, but only if you are lucky and only if you dig for it. Usually the error messages are cryptic and I find myself struggling to remember what kind of problem is represented by the symptoms I am seeing rather than having a message telling me what happened. However, these are relatively small problems and somewhat superficial even if they are frustrating. Support on the groups is often very helpful since everyone is using the same product stack (in most cases, the same versions too), and not mixing and matching libraries and library versions which can be a problem with Spring.
These are two very good frameworks, with only small issues between the two which makes any kind of summary or conclusion difficult to determine.
To get the obvious out of the way, Seam is jam packed full of features which many people might not appreciate and consider Seam to be bloated or heavyweight which seems nonsensical given that Seam is designed to be a complete solution in a box. That said, here we are looking at how well the frameworks tackle most of the work we do which is CRUD with logical flows between pages.
While Spring Web Flow might offer a more lightweight solution as an optional plugin to your web framework stack, it lacks the same deep integration that Seam has. Like many Spring libraries which can be bolted on to your projects it takes a little work and sometimes even some code to get the different pieces working together.
If you are using JSF and have no problems using EJB3.0 (but not requiring it) with a default stack of JBoss AS and Hibernate then Seam is well worth taking a look. I think Seam as a back end to JSF can be easier to use with the EL integration and the narrower focus of the framework makes it more powerful out of the box. There are still some points which need polishing up on, but for the most part they can be worked around.
If you favor Spring libraries then you can feel right at home with Spring Web Flow as it will meet most of your needs and integrates the same way most of the other Spring plugins do. The weakest points are exception handling, passing messages outside the flow and the issue of choosing between using a flow for each page or using another technique to create non-flow JSF pages. Any weak or missing points can probably be resolved through custom code.
The Spring solution isn't as well integrated as the Seam
solution which is good and bad for both. When Seam makes
a mistake it is hammered all the way through the
framework while Spring requires you to do more work but
gives you a little more control on a few features.
However, in some areas, Spring provides no support for
some features and expects the user to handle those
pieces. JBoss has some potential to add features to Seam
that can bring some aspects up to par with that of
Spring Web Flow. For example, defining variables in a
pageflow or even in
pages.xml
making it local to that flow or page, giving the
persistence context more flexible scopes, and allowing
multiple action calls during navigation transitions.
These are probably not without backwards compatibility
issues, but it would allow Seam to take on some of SWF's
strengths.
IDE support for Seam really shines since they partnered with Exadel and the code suggestion for Seam components (even EL expression within Java code) is wonderful. Spring also has IDE plugins but again suffers from the lack of deep integration in the Spring framework. This idea of a full stack offering deep integration while the lighter stack offers less integration is a common theme between these two solutions.
Regardless, both projects have been well received, and appropriately so as these are two excellent and freely available tools. While they both have strengths and weaknesses, they are both great frameworks.
| http://www.andygibson.net/ | Copyright © 2008 Andy Gibson |