Sunday, September 16, 2012

gwt mvp? nope. do it better.

This article describes my opinions on elegantly architecting complex UI applications using GWT without a massive framework.


GWT is great and its bundled MVP approach can be fantastic. It works well and when used correctly it greatly helps with your code's testability.

However, after using Google's MVP approach in a handful of applications to varying degrees, I've accumulated a few issues with it. Though I agree with heavy testing when it comes to backend business logic, heavy automated testing for UI is a pain and doesn't usually provide very much useful information. In my experience, most of my time on UI development is spent on the visual and "feel" component of an interface (and browser CSS quirks) - the issues that a test harness easily discovers are so rarely actual problems. Additionally, good development habits typically require countless iterations on UI based on user feedback; maintaining tests for these UIs can inhibit progress. The UI code itself should be concerned with presentation and handing data to/from underlying services. All the logical circumstances to test should be addressed by testing underlying service APIs, not with automated UI testing.

The well-accepted MVP approach doesn't lend itself very well to most of the interfaces I end up building. I find that for most of them the idea of a single activity and a single view don't work very well; modern UIs have many facets and are tightly coupled to their function by nature. Many interfaces have container widgets, each of which can hold various other widgets (and maybe more containers!). MVP constrains the interaction between the UI and the logic to a rigid, annoying interface. This just doesn't fit most non-trivial UI heavy applications.

Most of my applications would benefit greatly from fundamental support for nested "activity/views" so logical complexity can exist at any level in the UI in a clean, maintainable manner.

Thankfully, solving all this with GWT isn't very difficult. UiBinder is capable mitigating a massive amount of my complaints. Logic and presentation can be kept separate via the XML/Java binding that UiBinder does. There's no need to introduce an additional layer of interfaces. By building the entire application's functionality using widget composition, we keep logic near its related presentation. The high-level widgets are just selecting the components that will appear on the page and assembling them using layout widgets.

Using this approach, it's very easy to add in complex behavior to any spot in the chain of UI. This keeps logic and presentation very near each other; the UI is ultimately the glue of the application. It leverages services and action objects (see below) to accomplish tasks in response to events from the user. A textbox that automatically suggests names should be wrapped in its own widget (say, "NameSuggestingTextBox") that fully contains all its functionality (even knowledge of its datasource). It would probably wrap a more generic SuggestTextBox as well as inject (yes, use Gin!) the resources and services it requires.

As a general (somewhat obvious) rule, widgets should be constructed with only 1 real purpose; any combination of functionality should be surfaced using whatever combination of inheritance and composition necessary. A widget representing a high-level layout of your UI should not be concerned with the pixel-precision of its constituents; layout logic should be encapsulated in a layout widget employed by the former. The code inside of a widget should always fall into one of these categories:
  • Modifying the current widget UI
  • Invoking a service or action object 
The widget only gets control when initiated and in reaction to events it's subscribed to. A widget can often be listening to both UI events (@UiHandler methods) as well as application-generated events (by adding event handlers to services). A widget therefore defines the events it is able to process and handle, and it injects (depends on) the services that provide those events directly.

Most of the code inside of the widget is used to modify the UI in response to some event. If the UI-based code begins to get too lengthy in one widget, it should be extracted into a new object (a brand new widget or a utility class depending on the nature of the code). Any logic outside of UI work should be completely encapsulated inside action objects.

Action objects are (almost always stateless) objects that have a single "action-based" purpose. They inject all the required they require to accomplish this purpose. These objects will never interact with the UI directly; they may interact with services and cause events to bubble up to the UI but will never contain any UI code themselves.  Some potential action objects could be AccountCreator, GameJoiner, UserAuthenticator, etc. Any logical process offered by your application that can be invoked (typically via the UI or test harness) should be encapsulated as an action object. These are also the primary focus of any testing framework, as they bypass the actual visual widget assemblies. Maybe there's a different formal name for these that someone already coined? I don't know. Whatever.

Services are, unsurprisingly, services just as anywhere else. These are the only objects in your application that should hold any non-UI state and they'll typically be singletons. The current session, the current user, permissions, etc. should all be kept inside services. As always, with both services and action objects, as a matter of style make sure you're breaking all your circular dependencies! There should be no interdependence. Services should each expose event handler registration interfaces for the events they may throw; avoid using a global event bus. The only times you should touch a global event bus is under circumstances where multiple entities can each source events of the same type. However, under most circumstances, it's very easy to design a specific service that owns a particular event type. Dependency injection mitigates our need for a global event bus because we can directly inject the service we depend on (for example, a HeaderPanelUi might want to know when logins occur so it can display the account information so it injects the SessionService and adds a handler for authentication events).

The navigation clusterfuck is the last (arguably most important) issue remaining. You have a big collection of UIs. Some (many?) of them have a "content region" (a SimplePanel or something akin) where inner widgets can be placed ("navigated to"). We want inner widgets to be able to support arbitrarily deep levels of nesting. We want to be able to navigate from one UI to another with little effort. We don't want very much boiler plate code. We want GWT codesplitting to work simply and magically. We don't want to spend very much time reading documentation on how to get everything to work.

Well, I've been searching for this solution for awhile. I couldn't find it so finally I spent a weekend and wrote it. It's called Appli.

Appli Introduction

Appli is a tiny, tiny framework that glues together Gin, GWT, history management, code splitting, and navigation in a sensible way. Check out the tiny (very stupid) example application inside the Github project located here.

The heart of Appli's API is the AppliBasePlace. Every widget that can participate in navigation must have a place which is a subtype of AppliBasePlace. This place must be bound to the widget via the @AppliPlace annotation. Any place that follows this can be navigated to, and the widget bound to it will automatically get Gin dependency injection, bookmarkable navigation, and the entire widget's code and dependencies placed behind a GWT split point for faster application startup. The dependency injection system will use every Gin module it finds marked with the @AppliEligible annotation. However, since the Ginjector is generated at compiletime, you don't have direct access to it (it shouldn't be required).

If the component can accept a single child widget, it's considered a container interface and should implement AcceptsAppliUi  (i.e. any UI that has a blank area to contain various other inner widgets - like a tab panel UI). This is the primary building block for constructing nested interfaces.

The container widgets (those that implement AcceptsAppliUi) should not directly reference the widgets that they will contain. Instead, they'll only deal with the places bound to those widgets. This allows for code splitting to work properly.

Now we have a bucket of various AppliUi widgets (and some of them also implement AcceptsAppliUi). How do we define the structure of the page (i.e. what widget goes in each container) for a given Place? Simple! By modelling the page structure using the inheritance tree of the Place being navigated to. Subtypes of the place tied to a container widget represent those places that will be navigated to inside of that container.

So, here's how that actually works (pretend actually):
Example Application UI/Place Composition
The UI has an outermost element (MyAppUi), a tab navigation element (TabUI) and some feature that's being displayed on Tab 1 (AppFeature1Ui). AppFeature1Place is bound to the AppFeature1Ui it represents via the @AppliPlace annotation.

We want to show the user AppFeature1Ui, so we navigate to AppFeature1Place. Because this object is a subtype of TabPlace, which is a subtype of MyAppPlace, these interfaces are injected and created first (if they're not already displayed). First, the MyAppUi object is created and added to the root panel. Then, the TabUi is created and added to the MyAppUi. Finally the AppFeature1Ui is created and added to the TabUi.

After all UIs have been connected to the DOM, each will receive (sequentially starting with the outermost) a setPlace call where it can extract the information it needs out of the new place and update its UI accordingly.

The best place to start with the API docs is here. Example application is here.

General Notes and My Ideas on Best Practices:
  • Keep as much CSS as possible in the UiBinder XML file along with the page structure. Decide what aspects should be tweakable (colors, padding, etc) and extract them into variables or incorporate global styles (for only those tweakable aspects of a UI!)
    • Yes, this is a little against-the-grain because I'm telling you to keep the CSS along with the page layout XML. In reality, accumulating styles into one massive monolithic file is horrendous and very annoying to keep updated. Keeping related UI and style near each other is much more convenient.
  • Avoid using the global event bus!
  • Keep the in-widget code to a minimum! 
    • Using static "behavior" methods/classes is an easy way to cleanup the typical UI pattern where we're adding a few event handlers to hide/show/popup/whatever. for instance, TooltipPopupBehavior.attach(element, "I am tooltip text") is a trivial example invocation which might attach a TooltipPopup element element to an arbitrary element with the given text. Strive for single-line APIs encapsulating multi-step element configuration.
  • If a part of the UI doesn't make sense to navigate directly to, define its bound Place as abstract (though this typically will only happen on UIs that have no sensible default).