|
1:41:13 PM |
|
WebWrock!
I'm going to join in the game of talking about why WebWork rocks. Here’s my personal list of reasons (saving the best for last – no don’t look ahead yet).
Actions are not coupled to the web.
As already stated, an action implementation has no dependencies on the medium. Actions can be used for web-apps, distributed swing apps, SOAP services, EJBs, JMS actions, workflow engines, etc. Oh and actions don't necessarily have to be written in Java either.
Yes, I really have built an application (albeit proof of concept) that has a web front end and a .NET GUI front end that both use the same actions!
The power of commands.
The command pattern is pretty powerful. Entire actions can be serialized, transported over networks, persisted, etc. When building distributed systems a dispatcher could ensure that each action is executed on the most suitable server.
By extending Action to create UndoableAction, each action that is executed can be kept in a queue and undone if the user hits cancel at the end of their session. Actions can be decorated too... for example any action can be decorated with something that manages security or transactions in a declarative way (a la EJB).
Inversion of control.
As used in Avalon, actions have external context objects passed into them rather than looking them up. This means the container that hosts the object is responsible for passing in anything of interest. As an example, if your action cares about the user session, it implements SessionAware and has a setSession(Map) object. This will then automagically be passed in when the action is deployed and other containers (such as unit-tests) can pass in their preferred implementation. This typically leads to much cleaner and decoupled code. Okay, it doesn’t sound that exciting, but it makes a big difference when you’re coding.
Customization and extensibility.
The design of WebWork is incredibly simple and clean. Virtually all aspects of it can be overridden and extended.
For example, you could create your own interface such as ShoppingCartAware and have WebWork automatically pass in the current user’s shopping cart for any action that implements the interface (this is something I do frequently). Or you can create your own ActionFactory that dynamically loads actions from Jython scripts in a database (this is something I do not so frequently). Or you could implement your own error handling mecanism. Etc etc….
Test first
I saved the best for last. I can write user-interface actions test-first! Not just unit-test them, write them test first! Yes yes yes! Test first! (Okay, you get idea)…
Example story : To get to the download area, the user must enter a valid email address which will be added to our mailing list.
So, I write the tests:
public void testValidEmailAddress() { // setup mock. MockMailListManager mailListManager = new MockMailListManager(); // expect mailListManager.addAddress("joe@truemesh.com") to be called. mailListManager.addExpectedAddAddressValues("joe@truemesh.com"); // execute action EnterDownloadAction action = new EnterDownloadAction(); action.setEmailAddress("joe@truemesh.com"); action.setMailListManager(mailListManager); String result = action.execute(); // verify results assertEquals("downloads", result); assertNull(action.getError()); mailListManger.verify(); } public void testInvalidEmailAddress() { // setup mock. MockMailListManager mailListManager = new MockMailListManager(); // expect mailListManager.addAddress() to never be called. mailListManager.setExpectedAddAddressCalls(0); // execute action EnterDownloadAction action = new EnterDownloadAction(); action.setEmailAddress("aaaaaabbbbbbINVALIDEMAILxxc"); action.setMailListManager(mailListManager); String result = action.execute(); // verify results assertEquals("error", result); assertEquals("No email address entered", action.getError()); mailListManager.verify(); }
Obviously they don’t compile yet. Now I see I need to create a class called EnterDownloadAction with setEmailAddress(), setMailListManager() and getError() methods – so I do. (I already have a MailListAware interface which the action implements to ensure that WebWork will set the action at runtime). Because I’m passing the MailListManager into the action (inversion of control) rather than looking it up internally, I can pass in a MockMailListManager that will fail the test if its addAddress() member is called in the correct manner.
Now the test compiles – woohoo. Red bar! At least I’m testing something correctly.
Now I fill in the methods of the action and the bar goes green. And I’m happy. Well actually this happens in much smaller steps than this, but you get the idea.
Test first web-apps… once again… wooohooo!
|