On the way back from the GWT conference I felt pretty inspired, and decided to restart work that I had done on a GWT chess application. Like most apps that I build for fun, I didn't think the whole thing through before I started. In particular I was interested in the drag-and-drop aspect of the project, so I started there. Eventually I got to the point where I could move the pieces to only valid locations, but I had inadvertently made it difficult to tie in a back-end system to control a two player game. So on the flight back from the conference the refactoring began.
One of the sticking points that I kept running into was that the interactions between classes was getting rather complex and hard to follow. For example, when a user moves (drops) a piece it is handled by the drop controller. The drop controller needs to determine if it is a valid move and if yes needs to send the move to the server. This is kinda ugly though, having the drop controller call server-side services, and I quickly ended up having "business" code all over the place. So I started thinking about how to better control the application as a whole.
</narrative>I am a pretty avid Spring programmer and Spring has an application wide event system. It consists of three classes, ApplicationContext, ApplicationEvent, and ApplicationListener. The context is shared between all classes in the container, just like the application scope on a Java servlet. Using the context any class may register a listener, typically itself, to receive application events. Any class with access to the context may then create events and publish them to all listeners.
In Spring these events are published synchronously, meaning that the first listener needs to finish processing before the second listener gets to even see the event. This also means that the caller that published the event will block until all listeners have had a chance to handle the event. This model suits us fine for GWT since JavaScript is single-threaded anyway.
Time to look at some code, starting with the context.
import java.util.Vector;
public class ApplicationContext
{
private static Vector listeners = new Vector();
public static void addApplicationListener (ApplicationListener listener)
{
listeners.add(listener);
}
public static void removeApplicationListener (ApplicationListener listener)
{
listeners.remove(listener);
}
public static void publishEvent (ApplicationEvent event)
{
for (int i = 0; i < listeners.size(); i++) {
((ApplicationListener)listeners.get(i))
.onApplicationEvent(event);
}
}
}
In Spring the context would normally be injected into our classes, but as GWT lacks built-in support for dependency injection we simply make all of the methods static so that we can use it as a singleton. We have three methods; one to register a listener, one to unregister, and one to publish an event. Nothing fancy here, just the minimum required.
Next we need a listener interface and an abstract event base class.
public interface ApplicationListener
{
void onApplicationEvent (ApplicationEvent event);
}
public abstract class ApplicationEvent
{
}
I could have made the ApplicationEvent an interface, but my expectation that is at some point I may find it useful to add some helper methods in the future. Making it a n abstract class allows me to add functionality without altering the subclasses.
Now that we have all of the working parts it is time to start using it. In order to publish events we need to create classes that inherit the ApplicationEvent abstract class. In my case I decided to use fine-grained applications events, so I ended up with more than a few of them.
In the application I decided to have the entry-point class implement the listener interface, and registered it as a listener on start-up. In this application the entry-point class (acting as a controller) is the only the controller was listening to events published by various parts of the system.
Here are the two of the event class implementations used to create a new game or load an existing one.
public class CreateChessGameEvent extends ApplicationEvent
{
public CreateChessGameEvent () {
}
}
public class LoadChessGameEvent extends ApplicationEvent
{
private String gameId;
public LoadChessGameEvent (String gameId) {
this.gameId = gameId;
}
public String getGameId ()
{
return gameId;
}
}
Notice that the constructors of the specific events contain details that will be needed by the receiver of the events. Both of these events are triggered by clicking buttons in the user interface. Following is the code in the controller that receives the event and handles it based on the type of event.
public void onApplicationEvent (ApplicationEvent event)
{
if (event instanceof CreateChessGameEvent) {
service.createGame();
}
else if (event instanceof LoadChessGameEvent) {
service.loadGame(((LoadChessGameEvent)event).getGameId());
}
else if (...etc...) {
}
}
What isn't explicitly shown here is that service.createGame() and service.loadGame() both trigger asynchronous requests of the server. In the callbacks for each they simply send event notifications instead of handling the business logic themselves, which means the controller handles these events as well. The event used in the RPC callbacks is the ChessGameReceivedEvent, which includes the model for the specific game as part of it's properties.
So you might be thinking, why not just call the service methods directly from the button click handler and callback methods instead of sending an events back to a controller. If you are one of those people, consider this point. By using application events the user-interface and remote service calls have been completely decoupled from the controller and business logic. Decoupling is a good thing as it allows me to change the implementations of the service, user-interface, and controller independently.
Decoupling the view from the business logic results in cleaner code that is easier to maintain. In my case it simplified complex code, making it easy to refactor. For me it was a big win.
1 comment:
I developed a fairly complicated GWT app this summer and came across a different, but related, answer to organizational problems and the difficulty of building apps based on gwt-dnd.
Rather than build a separate infrastructure for notification, I decided to use the existing relationships implicit in the GUI -- the inheritance relationships between Panels and the things they contain.
In my layer that runs on top of gwt-dnd, DND behavior is controlled by classes implementing interfaces. The drop handler starts at the object that was dropped on (say a chessboard square) and goes up the chain until it finds an object that's got the appropriate authority to handle the drop event (the chessboard.)
Server communication was organized into a number of classes that implemented something like the "Command" pattern, but these packaged both the RPC call and the async handler into a single class. The async handler would then need to propagate change information to any UI elements that would care. Basically, UI elements would register themselves in multimaps, keyed by entity id's when they initialize themselves. When entity X changes on the server, each entity gets the new version of X pushed to it.
Perhaps it's not so flexible, but the strict correspondence between the GUI structure and event/notifier chains meant I could do major surgery on the UI and not have to touch a line of notifier code.
Post a Comment