Friday, January 11, 2008

A Year of Wicket

Update: screenshots of this product can be seen at IBM Redbooks. Note: there are many more screens in the application than are shown here. There's quite a bit of Ajax used that isn't evident from the screenshots, such as for page navigation and tabs.

I've been working with Wicket for almost a year. We've just released our first product that uses Wicket for the user interface, and so it seems like a good time to take stock. Unfortunately, it's not a public site, it's an installable enterprise product, so I can't show it to you. If you don't want to read further, here's the executive summary: Wicket rocks!

I was hired as the GUI Architect for this project. I came to it with many years of GUI experience, mostly using Swing, but without a lot of web development experience.

Because of my Java and Swing background, I was drawn to Wicket. It maps fairly closely to the Swing model of development. So does GWT, but when I evaluated it, it seemed so different from other J2EE frameworks that I felt it was a step too far. No HTML, and no WAR files, for example. This made my colleagues nervous, who were used to Struts and PHP. Me too, as a matter of fact.

I had done some pretty serious prototyping for another project with Tapestry, and there were certain things I liked, like runtime bytecode generation. But the learning curve was pretty steep. At one point I needed to create a custom component, and to do so I needed to learn about engine services and other arcane things that I felt made the process too hard. By contrast, custom components are Wicket's bread and butter, and they are very easy to build.

I also took a close look at JSF. It seemed overly complex to me, and not much of a departure from the Struts era. It came across as a technology designed by committee, with the combination of several complementary libraries required to get the job done, and there are still too many configuration files.

So we decided to use Wicket. Luckily, my management and colleagues were open-minded enough to give it a try. The application was a management console for an enterprise application. It did not require high user traffic, so were weren't concerned about what people talk about as the biggest issue with Wicket, namely scalability. We certainly have had no performance problems ourselves.

Right out of the gate I was able to create a framework with Wicket 1.2.6 that my colleagues could fill out, consisting of a navigation tree, a base page, database-driven tables, graphic buttons, page headers, tabbed panels, and a CSS-based look and feel.

One of Wicket's advantages is the strict separation of design from behavior, that is, HTML from code. While we did not have a web designer on the team who built the HTML (the developers did this), and therefore didn't get any mileage from the separation in that sense, we definitely gained from having all the behavior in Java code, because it gave us all the power of refactoring, compile-time error checking, and maximum reusability.

What I mean by maximum reusability is that I could create a component with quite a bit of logic behind it, e.g. a button that disables itself based on various combinations of server state, and all it would take to add it to a page would be <input type="submit" wicket:id="button" /> in the HTML, and new MyButton("button") in the Java code. In other words, it looks no different than adding an ordinary button. I could then change the behavior of MyButton as needed without affecting the HTML. And of course, there are no XML files to worry about keeping up to date, like struts-config.xml.

I made a big effort to encourage reusability on the team, which of course is one of the big reasons to use Wicket. So we made sure there was no cut-and-paste reuse, and instead if a component did not have a necessary feature, I spent the time to add that feature. This paid off in spades later in the project, since if a bug was found or the look and feel needed tweaking, it typically only needed a change in one place. The number of bugs related to errors in the Wicket code was tiny. All the bugs were about the business behavior, that is, misunderstandings between product management and engineering, which is where you want any bugs to be.

We had some unusual requirements, for example, there were some legacy applets that we needed to support because we didn't have the time to convert them to Wicket. It turned out to be easy to get the applets to communicate with the server using Wicket. We also embedded the applet page in a zero-height frame to avoid reloading the applets with each page view. The web pages were displayed in another frame. Again, Wicket was able to support this.

As a result of all this, we actually stayed ahead of our middle tier colleagues for much of the product cycle. I was very pleased with the result.

Observations

Models

Anyone who learns Wicket will tell you that the key to mastering it is understanding how models work. Models are the "M" in "MVC", and all decent frameworks are architected this way. But Wicket takes it to its logical extreme, meaning that all data, including resources, should be obtained through models, and it takes a bit of getting used to.

Fragments

The most complex reusable component we built was a custom form. In our case, I wanted all our forms to look the same, using the same mix of basic form controls. So I wanted a way to build forms entirely in code. I achieved this by using Wicket fragments. These are like mini panels and are very useful.

The result is that a client can create a very powerful form control using one line of code, e.g. a text field that has an attached label, marks the form as dirty using the onchange event, lays itself out in a pleasing way, can hide itself and its label dynamically (using Ajax), can disable itself based on session properties, and using a common CSS look and feel. Oh, and supports accessibility by using the label tag properly. No doubt I did it this way because of my Swing background, but it paid off for us. Interestingly, by the time it was done, it looked much like a GWT widget, but that's probably because GWT was also inspired by Swing.

Upgrade to Wicket 1.3

After our first release, we decided to upgrade, which I was nervous about, but it's always the best option when using an open source library so you can get support from the team and stay current with features and bug fixes. This turned out to be OK but not painless. Certainly it was a lot easier than going from Tapestry 4 to 5, which pretty much required a complete rewrite. The migration guide helped somewhat (it was incomplete and not very explanatory) but it still required a fair amount of work and investigation. One example: you now need to supply a MarkupProvider for fragments; I still don't know why, since it's not explained adequately anywhere. But we got it done without much code surgery, which shows that Wicket cares about backwards compatibility.

Ajax conversion

After we upgraded to Wicket 1.3, we decided the time had come to convert as much of our interface to use Ajax as possible in order to make the user experience more interactive and up to date. This is where Wicket really shone, but was also where we ran into some difficulties. It was surprisingly easy to Ajaxify the paging tables and to create lazy loading panels (panels that fill themselves lazily in when loading a page), a little less so to Ajaxify tabbed panels, and not easy to figure out how to create an Ajax button that pops up a confirmation (yes/no) dialog before proceeding with the submit action. The latter is because for someone like myself, who is much more at home with Java than Javascript, finding out how to do anything out of the ordinary with Wicket and Ajax is painful. This is one area where the docs and examples can be improved, particularly with so many applications going Ajax. In the end, though, we were very pleased with how little code change it required to the application as a whole.

Separation between presentation and business logic

One of the criteria in my choice of Wicket was, does it promote good separation between presentation and business logic? Our experience says yes. We have a well-defined API layer that the middleware provides, and the Wicket code makes calls to it (exclusively from Java code) at exactly the right level of abstraction. For example, when you add an employee to the employees table, you would do it in the onSubmit() method of the Add Employee button. Furthermore, you handle exceptions in the same place, which 90% of the time, add an error message to the feedback panel on the same page. It ends up being a small amount of localized code, all in the same place.

Resources and Localization

Wicket has a nice model whereby it looks for resource strings in the properties file for the component in question, and if it doesn't find it there, it walks up the component hierarchy to find it. This worked well for us, but sometimes it can be difficult to work out where Wicket is trying to find a resource. Some people prefer to put all strings in one file; Wicket supports this too, but I feel it's easier to tame resource string proliferation if the strings are closest to where they are used.

We made use of the "style" session attribute for branding purposes. The style is intended to "skin" your application but it works well for branding too. It works by adding another suffix to the filename of a properties file that it's looking for.

One of the things that could be improved is the explanation of resource-related APIs. For example I still don't know when I should use component.getString(), ResourceReference, or StringResourceModel. As with most mature frameworks, over time the number of APIs has proliferated, and sometimes it's hard to keep them straight. This is a pity given Wicket's very clear mission of simplicity.

Wicket Bench

We ended up using a free Eclipse plugin called Wicket Bench, which is surprisingly useful. When you open a Wicket page (a Java class), it looks for other files with the same name but different suffixes (e.g. html, css, properties) and loads them in different tabs in the same view, so it's easy to switch between them.

Maven

When we upgraded to Wicket 1.3 we decided to switch from Ant to Maven for our builds, with mixed results. First of all, the middleware team didn't want to switch to Maven, and Maven doesn't play too well in a heterogeneous environment. Second, I found I had to add Ant tasks to Maven to do simple things like deploying my WAR file to JBoss. It blows me away that Maven doesn't have a simple copy task built in.

On the plus side, it was trivial to upgrade from Wicket 1.3 BETA, to RC, to GA by just changing the pom.xml file. And the Maven integration with Eclipse is very nice.

Links

Tip: make sure you create links lazily. That is, don't create the web page that is the destination of the link until onClick() is called. This was our biggest performance bottleneck. Fortunately, Wicket 1.3 has deprecated the constructor that takes a WebPage and so it encourages you to do the right thing.

Data Tables

We've found there tends to be a proliferation of the trio of classes that implements a paging data table, namely DataProvider, DataPanel and DataView, along with their supporting properties files. Keeping them in sync can be a pain. It would be nice if there was a single class in the API that implements the common case where all three are used once to implement one table, i.e. where there's no reuse of a DataProvider for other tables.

Things I'd love to see

I can't wait for the release that was going to be Wicket 2.0, namely a reorganization of the API to provide better error messages and support for generics. One of the hardest things to deal with is the obscurity of error messages. In this one sense I preferred Tapestry.

> Jonathan, here's an example of an error message that isn't helpful, the real problem being that not a single call in the stack trace goes through my code

Ofcourse not every error will include at least one frame from your code. Some errors simply happen later. In this case: you define the component hierarchy, later Wicket iterates through it as it renders. It is during this rendering process that you get this error - none of your code is actually executing.

hi julian, congratulations, nice summary! btw. i think the fragment constructor got deprecated as the fragment's markup sometimes couldn't be found, especially when you use nested panels containing fragments. i ran into this problem once :-)

Great post. It is great to see that you/ your team 'gets' developing custom components. A very good way of using Wicket is to view components as building blocks you can use to create your own DSL (well, it's not really a language of course, but I hope you get my point). How you build up a custom form to encapsulate your common case is an excellent example of that!

Buddy: unfortunately I'm no expert on Tapestry and my experience is limited to what I said in the piece. But the summary is that I find the way the code ends up looking in Wicket is more rational to me, in that it's strictly component-based, and all the action takes place in the code, whereas with Tapestry there's still code that ends up in the markup.

All well said. We have been developing applications using wicket since June 2006 and are extremely happy. However, having decided to shift to 1.3, the jasper reports integration with wicket does not work. Result - Our reports using Jasper dont work in wicket 1.3