Discover Seam and Sew Up Your Java Projects Faster than Ever

Discover Seam and Sew Up Your Java Projects Faster than Ever

ecently, a friend called to tell me about a huge turnout at an Atlanta JUG meeting to hear about a new product from JBoss called Seam. I went to my keyboard and after just 20 minutes of reading about Seam, I was very impressed. For starters, Seam is based on the lightweight standards in Java EE 5 (J2EE 3) like the new entity bean spec, JSF, annotations, interceptors, and session beans. Like Spring, Seam uses inversion of control but unlike Spring, Seam allows injection of stateful objects. Much of the data movement and framework/API manipulation work that enterprise Java developers have drudged through for 7 years disappears with Seam.

Use cases and user stories record requirements in a conversational manner but Seam is the first product I’ve seen that facilitates coding in a conversational manner. For the first time, page-level and BPM-level interaction can be a first-class entity in my application. Use cases and user stories actually become the models for code.

On a personal level, I was most impressed when I realized how much code my development team could have saved over the last 3.5 years?perhaps as much as 40 to 60 percent of our code would be unnecessary using Seam.

Later, my own hands-on experimentation with Seam proved that its promises of less coding and easier development are quite true, which I hope to demonstrate in this article.

Editor’s Note: Mark Smith is a Director at Valtech Technologies, which has a consulting partnership with JBoss, makers of Seam.

Seam Overview
Seam has a lot going for it, but to my mind these are the key:

  • based on Java EE 5 standards
  • a declarative state model
  • subversion of control (also called bijection)

Seam builds upon the concepts that the drove the Java EE 5 spec?lightweight development and ease of use for programmers. Seam has a powerful integration framework for JSF and EJBs. The product eliminates the glue code usually written to integrate these products. The developer is not spending time moving data out of objects and into forms or JSPs but is focusing on delivering business value.

The cost savings Seam provides at the integration level is found mostly in the Hibernate/EJB 3 entity bean approach that Seam is founded on (see Table 1). It removes the logic required to move data from a JDBC call into an object and back into JDBC. It provides complex relationship management at a class level and hides the details of foreign keys and the code required to manage these issues in past JDBC applications. While Hibernate introduced these concepts, with the 3.0 version of the EJB specification, they’ve been adopted as Java standards. (See “Simplify Java Object Persistence with Hibernate” for more on this model.)

Table 1. Comparative Line Counts with JDBC Calls vs. an EJB 3/Hibernate Approach.

Class for Customer Integration with JDBCLine Count

Class for Customer Integration with HibernateLine Count
Customer.java179 the query method, none of the front end controlling logic)66

Seam’s declarative state model (see Figure 1) allows you to declare the context with which to manage an object with state. The context can be application, session, conversation, page, event, BPM, etc. By declaring the context of the state, Seam will keep the state around when it is needed and clean it up when you are done. State management eliminates a host of bug and performance problems associated with second-level database caches, stateful session beans, or http session. It also removes the need for the glue code associated with using APIs.

Figure 1. Inspecting Seam: Seam removes the need for much of the glue code between JSF and the rest of the Java EE 5 specification. It introduces Context management of state into a complex Java EE 5 Web application. SOURCE: JBOSS.

Inversion of control (IOC), a concept made popular by Spring, is often expressed in a Hollywood term: “Don’t call me, I’ll call you.” The IOC concept moves certain types of work out of application code and into frameworks or containers, moving management of certain APIs away from your code and into a container. Dependency injection, a form of IOC, moves creation or management of identified objects to the IOC container. Whereas you once had to write code to handle the fetching of objects and APIs, dependency injection figures out what objects you’re about to use, creates them or gets them, and provides them to the application code. In this way, the objects in your application code have no dependencies on the APIs that create them.

The beauty of the IOC and dependency injection concepts is manifest in Seam with subversion of control and bijection. The difference between IOC and SOC is the ability to “out-ject” as well as inject objects. An IOC container creates or reuses a single instance and injects that into the object needing the resource. Out-jecting allows me to put an object containing interesting state into the container for it to use for injecting later. While IOC only put resources into objects, Seam allows the transfer to go both ways.

Seam uses the following annotations to accomplish injection:

  • @Name and @Role identify classes that can be injected.
  • @In injects objects.
  • @Out (which has no equivalent in Spring) exposes the changes made in the injected object to other objects that may need to see them.
  • @Scope puts the Seam Component (identified with @Name) into the declarative state management framework.
  • @Scope(CONVERSATION) puts a Seam Component into the state management framework at the conversational level.

Seam makes extensive use of annotations, including both EJB 3.0 annotations as well as its own. Putting database mappings and other API configuration information in annotations reduces the complexity of using third-party frameworks and improves one’s ability to read and understand code because it is no longer in a separate XML file.

SOC combined with the declarative state model is a powerful combination. It is how you begin to code in a conversational manner. This facilitates complex page flow, particularly when using JBoss’ jPDL product, for which Seam provides native integration (see Figure 2). JBoss’ jBPM fits into the programming model with ease, where in the past it BPM types of tools never seemed to mesh well with the old programming models.

Figure 2. Managing Workflow: Seam has the ability to manage page flow graphically through jPDL. jPDL is also used to manage workflow in jBPM.

Managing Page Flow with Seam
There are two ways to manage page flow with Seam. The first is very basic: When a button is pushed, it is passed a string that is then mapped to the next page. The routine that returns the string is where the complex page flow logic resides. In this example, I use the basic XML file, also called the stateless navigation model:

                        editCustomer            /editCustomer.jsp                            selectCustomer            /findCustomer.jsp                            findCustomer            /findCustomer.jsp                        /editCustomer.jsp                    find            /findCustomer.jsp            

The other Seam page flow model, called jPDL, is an XML file that defines a process definition language. jPDL has a very nice graphical interface, which is a powerful tool when dealing with complex page flows.

An Unseamly Experiment Begins
In his book, “Beyond Java,” Bruce Tate uses an example in which he builds an application in J2EE in several weeks and then builds the same application in a weekend with Ruby on Rails. My goal in this article was similar: I will show an example of the application that I have been working on for the last 3.5 years?a Web-based car rental application for a major car rental company, refactored from COBOL to J2EE?and then the same functionality written using Seam. And I’m challenging myself to do it in a 48- to 72-hour time frame.

The example application, of course, is not the exact commercial app my team has been working on for so long but rather a scaled down version. However, it uses the general concepts that most layered large J2EE architectures are based on.

This architecture uses a 5+1 layer pattern. Each Layer has certain responsibilities assigned to them. Layered architectures manage dependencies by ensuring that each layer depends only on the layer immediately below it. Upward dependencies are not valid. It is an effective way to manage dependencies, manage the proper assignment of responsibility in a large development team, and decompose the problem set in a common way across the team.

Each layer needs to transfer data and that data tends to look pretty much the same. For this reason I’ve created a +1 layer that allows all components in all layers to have dependencies against it. This layer contains data files that have no real logic in them (see Figure 3).

Figure 3. Layered Architecture: The 5+1 layered architecture is shown with each layer identified. Components are placed in their layers and dependencies are identified between components.

As you can see in Figure 3, the top layer is the presentation layer, which is based on Struts. The second layer is the application coordination layer and contains the integration with the security system. Session EJBs manage interaction with this layer. This layer also contains the application-specific logic and manages interaction between the business domain entities. The third layer is the business layer and it contains the major domain entities within the enterprise with a set of reusable logic and functionality. The fourth layer manages communication between the rest of the system and its external resources. This layer is based on the DAO pattern and each external resource has one or more DAOs to manage the communication. The fifth layer is the external resources needed. Some of our applications have a single database as its external resource and others use as many as 18 different external resources. We have multiple applications based on this architecture which reuse several components and DAOs.

The database layer comprises four tables: the Reservation, the Location, the Customer and the CarClass. This database schema is used for the Seam application.

The application flow begins with the user associating a Customer record with the Reservation Record. The user then associates a pick-up and drop-off Location Record and the date and time for both with the reservation. A CarClass Record is associated with the Reservation Record. Based on the car class rate and the duration of the reservation, an estimated charge is calculated and put into the reservation table.

Once the reservation entry is persisted and completed it should contain a customer reference, a car class reference, a drop-off and pick-up location, the drop-off date and time, the pick-up date and time and finally the estimated charges.

The Weekend of Seam Arrives
As I sat down to get started on my 48- to 72-hour Seam experiment, I decided to use the Hibernate code generator to create the beginning of my code base. This tool has the option of creating a Seam skeleton application and is part of the JBoss IDE JEMS product. The generated components for each database table are:

  • the Hibernate object containing the actual data from the row in the table
  • a Finder Component
  • an Editor Component
  • a Selector Component

In the case of my Location Table, the Reverse Engineering tool generated two JSF pages, four classes, and two generated interfaces (see Figure 4). I’ll walk through the various generated components as an example of what you can expect:

  • is the Hibernate component
  • is an interface for the LocationFinderBean.
  • is the session bean that contains database lookup code and page flow logic for the finder JSP page.
  • is an interface for the LocationEditorBean
  • is the session bean that contains the logic to create or modify a location and manages page flow for the edit JSP page
  • contains an interface and static inner classes that implement the interface allowing multiple rows to be listed and then selected. It also contains logic for screen titles, button labels and text labels.
  • editLocation.jsp and findLocation.jsp are the JSF pages
  • editLocation.jsp allows you to create or update a specific database table row/specific instance of
  • findLocation.jsp allows you to provide search criteria, page through a list, and select a specific database table row/ specificinstance of from the list.

Figure 4. Generating Code: The code generator begins with a basic database table represented by the “Location Table.” Once executed against this table I get two JSF pages and Seam code to support the JSF pages. The editLocation.jsp provides a web page that can create or modify a row on the “Location Table.” The findLocation.jsp provides a web page that can search for rows in the Location Table, page through those rows, and select one of those rows.
Figure 5. Picking a Car: The screen shot shows the user selecting a car class from the Create Reservation page. This launches the findCarClass.jsp page. Click on the “Find” button to execute a search, then click on the “Select” button beside the car class desired.

Once the creation was created, compiled and deployed in JBoss, the application could now search, page, add, delete, and update the Location, CarClass, Customer, and Reservation tables. This took less than an hour to complete.

I didn’t like everything. On the Reservation table I had to enter the primary key of the pick-up and drop-off location, the car class, and the customer in order to associate them with the reservation. Also my reservation object used integers rather than objects as references to these classes. I wanted to generate code that would manage that relationship well. I had seen examples where this was generated, therefore I knew it could be done. I decided to use foreign keys in the database.

I created the foreign key to CarClass, generated the code, and was impressed with the results. At that point I had a button attached to my create reservation table that took me to the findCarClass.jsp page. From that page I could search for CarClass objects and select the CarClass I wanted to associate with my Reservation object (see Figure 5). Again this was all accomplished in less than an hour.

At this point I’m thinking all I have to do now is add the three additional foreign keys (one for the customer table and two for the pick-up and drop-off locations) and I’m 95 percent done. That’s where I ran into my first snag: After adding my new foreign keys, the generated code wouldn’t compile.

As I dug into the errors, I first discovered that because I had a foreign key to the location table for the pick-up and drop-off location, I had duplicate methods on several objects managing the interaction between location and reservation. It seemed like a simple enough error to fix?just comment out the extra methods and everything should work–but going through the flow of the generated code was slow work. After four hours of debugging the code compiled and I called it a night.

The next morning I deployed the code but found that I got an exception every time I added a location to the reservation. By lunch, about four more hours, it was clear that my attempts to improve it were actually making it worse.

I decided to abandon my current approach and try something more agile. First I would remove one of the foreign keys from the location table and generate code that compiled and deployed. It took less than 10 minutes to generate the site and provide the integration of the CarClass, the Customer, and one of the Locations. With just the one location left to integrate, I decided to start in the jsp and add the functionality by hand, but without changing any back end code at this point.

This action resulted in the following JSF code:


reservationEditor.instance.pickUpLocation == null}"/> reservationEditor.instance.pickUpLocation}" rendered="#{ reservationEditor.instance.pickUpLocation != null}" rowClasses="rvgRowOne,rvgRowTwo"> ... reservationEditor.pickUpLocation}" value="#{msg.View} #{msg.Location}"/> selectPickUpLocation}" />

I had the hooks in the front end to display the pick-up location so now I needed to add methods to the Reservation components to define a pick-up location. I modified so that the pick-up and drop-off location variables were now both of type Location because one of them was int which represented the primary key of the location table. And the getter and setter methods for these variables were modified from int to Location type.

I added methods selectPickUpLocation(), pickUpLocation(), selectDropoffLocation() and dropOffLocation() to, all of which were essentially copies of their original methods selectLocation() and location(). While they were named differently, they still had the same implementation. I modified the JSF code to work with the new methods and deployed successfully.

The new methods in ReservationEditor used the LocationSelector interface and its static inner classes. One inner class helped integrate the Reservation and Location relationship. I made two copies of that inner class and renamed them for use in selecting the pick-up and drop-off locations. I used annotation @Name to provide a way to identify the class when injecting an instance.

     @Stateless     @Name("reservationPickUpLocationSelector")     @LocalBinding(jndiBinding = "com.devx.res.example.ReservationPickUpLocationSelector")     @JndiName("com.devx.res.example.ReservationPickUpLocationSelector")     @Interceptors(SeamInterceptor.class)     public static class ReservationPickUpLocationSelector implements LocationSelector {     @Stateless     @Name("reservationDropOffLocationSelector")     @LocalBinding(jndiBinding = "com.devx.res.example.ReservationDropOffLocationSelector")     @JndiName("com.devx.res.example.ReservationDropOffLocationSelector")     @Interceptors(SeamInterceptor.class)     public static class ReservationDropOffLocationSelector implements LocationSelector {

Experiencing the power of annotations and how Seam uses them is where I began to really find Seam cool! I had to modify the methods I added in the to work with the selection inner classes I had just created. I changed:

     @Begin(join = true)     public String selectPickUpLocation() {          CONVERSATION.getContext().set("locationSelector",Component.getInstance("reservationLocationSelector", true));          return "selectLocation";     }     @Begin(join = true)     public String selectDropOffLocation() {          CONVERSATION.getContext().set("locationSelector",          Component.getInstance("reservationLocationSelector", true));          return "selectLocation";     } 


     @Begin(join = true)     public String selectPickUpLocation() {          CONVERSATION.getContext().set("locationSelector",Component.getInstance("reservationPickUpLocationSelector", true));          return "selectLocation";     }     @Begin(join = true)     public String selectDropOffLocation() {          CONVERSATION.getContext().set("locationSelector",          Component.getInstance("reservationDropOffLocationSelector", true));          return "selectLocation";     } 

With the simple change shown above I’m using a completely different instance of locationSelector for the findLocation.jsp and ReservationEditor. The different instance was put into the conversational state context depending on which button I selected on the editReservation.jsp. Whenever ReservationEditor or its client, createReservation.jsp, referenced “locationSelector” they would get the correct instance. The Selector managed things like button labels and page titles so that the jsp page was reused but could identify which location was selected and for what reason.

I had one problem left to solve. The screens titles for the selectLocation.jsp would change based on which button was selected on the createReservation.jsp page. I needed one line in the whole class to change based on whether it is a pick-up, drop-off, or general location. I took the easy route and generated two new classes with a one-line change between the two files. I had to modify the reservation editor to use the two new classes. In, I changed:

     @In(value="locationEditor",create = true)     private transient LocationEditor locationEditor;     public String pickUpLocation() {          locationEditor.setNew(false);          locationEditor.setInstance(instance.getPickUpLocation());          locationEditor.setDoneOutcome("editReservation");          return "editLocation";     }     public String dropOffLocation() {          locationEditor.setNew(false);          locationEditor.setInstance(instance.getDropOffLocation());          locationEditor.setDoneOutcome("editReservation");          return "editLocation";     }


     @In(value="pickUpLocationEditor",create = true)     public String pickUpLocation(LocationEditor locationEditor) {          locationEditor.setNew(false);          locationEditor.setInstance(instance.getPickUpLocation());          locationEditor.setDoneOutcome("editReservation");          return "editLocation";     }     @In(value="dropOffLocationEditor",create = true)     public String dropOffLocation(LocationEditor locationEditor) {          locationEditor.setNew(false);          locationEditor.setInstance(instance.getDropOffLocation());          locationEditor.setDoneOutcome("editReservation");          return "editLocation";     }

While Spring has the ability to inject into a method, it isn’t recommended. With Seam you can. With that I could inject a specific instance of the location editor that I wanted into the method signature. In the “before” code you can see injection into a class variable; in the “after” code, injection into a variable on the method signature.

By now my Seam tally was up to 16 hours. In that time I was able to generate an application that could CRUD, search, and page through four database tables. It could also manage object-level references from the reservation object to three other objects?one of which held two references. The equivalent portion of the original application was developed in 50 hours with less functionality than my 16-hour investment in Seam.

Comparing the Two Implementation Approaches
To demonstrate the amount of coding saved in the Seam experiment, it’s useful to take a comparative look at portions of the application.

Example No. 1
The original application did a lot of work involving the maintenance of state for the HttpSession. It involved a wrapper around HttpSession that added state and associated with an event. When an event occurred the wrapper would clean HttpSession, removing all events at that level and lower. With Seam, none of that needs to be written or maintained.

To wit, the following code from the original application uses SessionManager (the HttpSession wrapper) to get XDelegate out of httpSession. If it’s not there or is not the right type I create a new instance and put it into httpSession. The EventLevel is Screen so when moving between screens this will be removed from http session automatically.

XDelegate delegate = getDelegate(request);…protected XDelegate getDelegate(HttpServletRequest request){Object delegate = SessionManager.getAttribute(request, DELEGATE_KEY);     if ((delegate == null) || (!(delegate instanceof XDelegate)))     {          delegate = new XDelegate();SessionManager.setAttribute(request, DELEGATE_KEY, delegate, EventLevel.Screen);     }     return (XDelegate) delegate;}

In Seam, the code looks like:

@In(value="xDelegate", create=true) @OutXDelegate delegate;…@Name("xDelegate")@Scope(PAGE)public class XDelegate implements AbstractDelegate{…}

The @In injects XDelegate into the object. If it doesn’t exist, (create=true) creates the object for me and @Scope associates the instance at the page level. So that when I go to a new page the old delegate will get cleaned up and a new one will be generated when needed. I don’t have to write any code over and over to confirm receipt of a value or to check if the value is of the right instance. I don’t have to write any code to manage the interface to session manager or debug it or make sure the objects are being cleaned. In the Seam example, I just annotate the objects I want managed and injected as well as the instances where I want that injection or out-jection to occur.

Example No. 2
With a team of more than 60 developers, the original application frequently suffered from a lack of cohesive methods. ReturnResults objects are used frequently in the application to record activity results in a central location. And these objects return all over the place to collect and hand back the work done on the data. The Map, SessionKey, and security objects created similarly messy issues. These three objects are passed around in all my methods between layers and way too many methods in between. While some methods in the chain of method calls will need these objects, most don’t. Still I need to pass them around because the next layer might need them. This clutters up my method signature with irrelevant objects. I can now inject these objects exactly where I need them and no where else. I use @Name to identify which objects I want to be a part of my declarative managed state. I use @Scope to tell Seam when to create new ones and when to throw them away. The same is true for ReturnResults objects; I simply inject the ReturnResult object anywhere I want it and outject any changes I want.

Managing Conversation State
I would be remiss if I did not mention that you can begin a conversation with the @Begin annotation and end a conversation with @End annotation. That’s where the ability to code in a conversation paradigm begins to happen. Take a look at the requirements for my system in Table 2.

Table 2. These system requirements will be used to design the application’s business logic.

1. The user indicates the creation of a reservation.2. The system responds with a reservation Create interface.
3. The user searches for a customer.4. List’s Customer’s matching criteria
5. The user identifies Customer6. The system associates customer information with new reservation
7. The user Searches for pickup location8. The system lists locations matching criteria
9. The user identifies pickup location and pickup time10. The system associates pickup location and time with the reservation.
11 The user searches for drop off location12. The system lists locations matching criteria
13. The user identifies the drop off location and time14. The system associates the drop off location and time with the reservation.
15. The user searches for car class16. The system lists car class matching criteria
17 The user identifies car class18. The system associates Car Class with Reservation and calculates estimated charges.

Here you see a series of interactions to create an item of business value to the stakeholders of the system. But how does that map into code? In the original application, the Stateful Session bean has a significant amount of that logic mapped into its interface:

public CustomerListTR retrieveCustomers()public ReservationTR addCustomer(ICustomerVO cust)public AllLocationsTR retrieveAllLocations()public ReservationTR assignPickUp(ILocationVO loc, Date dat)public ReservationTR assignDropOff(ILocationVO loc, Date dat)

Behind those methods is a significant amount of code that gets the component and asks it to do something, manages state, and more. In the original application most of the interactions with external systems involve an asynchronous method call. For performance reasons I needed to keep the response of the asynchronous call around over several user-initiated interactions with the system. I have tried storing that state in http session, stateful session beans, and entity beans but none of these solutions are conversationally oriented. They require me to write code to put the object in my state management system and to take it out once I’m done. And any user who exits the conversation in a way that isn’t explicitly handled, could create troublesome bugs.

In the Seam app I’m injecting objects as they are needed and I’m starting and ending conversations as needed. This allows me to focus my objects on doing the business work not on managing conversation state in a state management model that doesn’t support conversational state. Now my code begins to look like my requirements. Very @powerful!

@In(value="reservationFinder", required = false)private transient ReservationFinder reservationFinder;@Begin(join = true)@IfInvalid(outcome = Outcome.REDISPLAY)public String create() {@IfInvalid(outcome = Outcome.REDISPLAY)public String update() {@End(ifOutcome = "find")public String delete() {@End(ifOutcome = "find")public String done() {@In(create = true)private transient CustomerEditor customerEditor;public String customer() {@Begin(join = true)public String selectCustomer() {@In(create = true)private transient CarclassEditor carclassEditor;public String carclass() {@Begin(join = true)public String selectCarclass() {@In(value="pickUpLocationEditor",create = true)private transient LocationEditor pickUpLocationEditor;public String pickUpLocation() {@Begin(join = true)public String selectPickUpLocation() {@In(value="dropOffLocationEditor",create = true)private transient LocationEditor dropOffLocationEditor;public String dropOffLocation() {@Begin(join = true)public String selectDropOffLocation() {

The Front End
Finally, I’d like to look at the front end of my application to see how Seam performed there. The AppCoord layer (see Figure 3) of the original application communicates with several different types of clients including Struts, Swing, SOAP, and JMS. To make that burden easier, data specific to a given request is moved from a value object (VO) to a transfer object (TO). I provide TOs to hide modifications to VOs and provide an abstraction layer to decouple my VOs from the clients of the AppCoord layer. It also means that I only exchange the data needed to perform the selected operations.

The TOs wrap all the primitive types along with String and Date so that I can associate messages, problems, and decoration indicators with each field. The wrappers allow me to associate this information at the class level. Creating the code to move this information from the ReturnResult and VOs into the TO and wrappers takes a long time.

With Struts, for example, you have to build the Form object, the Action object, and the JSP itself. To get the data ready to be displayed for my Struts action I move a set of data out of one or more VOs into a TO. I also move error messages and field decorator indicators into the TOs and wrappers. I hand the TO to a struts action which then moves all that information into the Struts-based form object. All that data movement is code that must be written, debugged, and maintained.

All that code to move all that data is gone in Seam (see Table 3). Seam provides extensions to JSF that also remove the need for Struts actions. The beauty of the Seam framework is that I could add the TO concept if I wanted. If I were sharing data in a JMS- or SOAP-based message I probably wouldn’t supply VOs, but the power of the integration with Seam components and JSF is too powerful to ignore.

Table 3. Comparative Line Counts with Struts vs. Seam.

Class for Struts (to list customers)Line Count
Class for Seam( to retrieve, list, and select customers)Line Count

Sewing Up the Seam
One of my favorite things about Seam is that it fills in the holes that Java EE 5 leaves open. Much of the code developers typically write to manage APIs and frameworks is managed by Seam, leaving time to focus on bigger picture issues. It does this without forcing you into a specific type of architecture.

While API management is a big win, Seam’s contextual management of state makes coding feel and look closer to the requirements. I found it much easier to concentrate on functionality rather than how the application server implements a Java specification. I’m not bogged down with EJB complexity as an added blessing.

There are, however, a few things that I don’t like about my SEAM application. The generated code could have a better separation of concerns. For example, the Editor and Finder do what you would expect them to do but most of their methods return strings. Those strings are used by the page flow model to determine the next page to display. Its not only doing editing and finding, it’s also doing page flow and paging.

The selectors not only help you select an object from a list, but they also determine the labels on buttons and page titles. The worst part for me was finding that the LocationSelectors make calls back to the ReservationEditor. I feel this violates basic OO principles of proper object relationships. I would prefer code that deals with the back end, such as building SQL to do searches and lists in the finders and maintain a separation from the code that determines which page to go to next. The selector object is doing three different things; the code to do those things should be in three different objects.

Finally, the generated code uses the div tag to do layout and therefore did not look good in IE but did look good in Mozilla Firefox. Most importantly, keep in mind that Seam is beta.

Those issues aside, Seam can reduce the amount of code you need for your integration work, your business logic, and your front end. In the Customer object of my application alone I saw a difference of 800 lines. In a large system with hundreds of key domain objects and millions of lines of business logic, I estimate you can expect a 40 to 60 percent reduction in code lines. And considering that the more lines of code you have the more expensive your application is to maintain, Seam should by all accounts deliver ROI quickly.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist