Brian's Waste of Time

Tue, 12 Apr 2005

Building Webapps

Some guidelines when building web applications.

  1. Seperate view and model state.

    Model state is the stuff associated with the problem domain. If your application is a fancy bug tracker, then the model state is bug statuses, notes, workflow states, transition histories, etc. View state is the current sort order for a list of bugs, the preferred number of bugs on a screen, the logged in user id, the alerting rules, etc.

    There is also persistent view state -- the "where someone visited" log, the preferred home page, a set of filters to be applied by default. This is stuff which usually makes sense to stick in the database. This should not be managed the same way as the model state. Typically the persistence requirements are very different for view state, so different that you will probably want to use a different persistence mechanism. This is okay. Really, it is.

    Of the frameworks I've used, Tapestry makes the distinction between view and model state the easiest to see. View state is stored as component properties, model state is... part of the model. The next Tapestry is adding even better support for how vie state is managed. I'm looking forward to plugging in database (with good caching) backed properties, for instance, so that for a given user the property is quite fixed.

  2. Manage view state like manually managed memory.

    If view state is seperate from model state, we need to figure out how to manage it. Most often it is plunked into the session abstraction (HttpServletSession in Java Servlets, for example). This is great, it is what the session is for. On the other hand, most often it is treated like a novice's C or C++ heap -- it leaks, terribly.

    Until modal webapps catch on in a bigger way, we cannot make use of the stack for managing this memory -- we need to be disciplined about memory management. The easiest way is the good old "if you allocate it, you destroy it" technique. This works well for simple things, like wizard style multi-page forms, but can get more complicated for other bits. Implementing a reference counting mechanism isn't difficult (if a component cares about it, increment the count, if the component no longer care, decrement, have a servlet filter do the GC on the session after each request) though it feels awkward to work with when you have GC in most languages nowadays.

  3. Never change model state with an HTTP GET.

    This is a strong suggestion in HTTP, and a bloody good one. Any complex interface component (be it a Tapestry, JSF, Wee, Seaside, or Rails component, a tag library, or whatever Zope calls em) will need to interract with the user internally -- if just to handle sorting of a table or some such. This requires being able to get back to the place it was without changing anything. This is GET.

    There are lots of other good reasons for not allowing model state to change with a GET -- HTTP proxies, caches, browser caches, back buttons, spiders, replay attacks, bookmarks... the list goes on. Changing model state with a GET is bad. Don't do it.

    The jury is still out with view state. The purist in mean says no, the pragmatist is beating that purist with a lead pipe though.

  4. Treat system tests like an alternate user interface.

    There are lots of levels of automated testing. Uint tests, integration tests, system tests, etc, etc. System tests for the application layer (ie, where the model state and business logic is implemented) should be treated as an alternate user interface. That is, they should not use any artifacts from the view. This doesn't mean they shouldn't rely on the javax.servlet.* artifacts (they shouldn't but that is a different beast altogether), they shouldn't rely on the web actions (or pages, or components, or controllers, or whatever you call them in your tool of choice), the view components, the view level interfaces and classes, etc.

    All the business logic and state should be programmatically accessible without duplicating logic from the view components. It is tough to get this right without simultaneously developing multiple interfaces. Treating the system tests as that alternate interface does a couple things. One, they are fantastic integration tests for the actual business core. Two, they are that second interface that lets you accurately gauge what code belongs where. Win, win.

  5. View complexity is usually higher than model complexity.
  6. The user interface is usually much more complex then the business logic. On the other hand we usually spend much more time hammering out a correct model for the core then we do modeling the user interface (code, not look and feel). Unless you are writing a simulation, complex forecasting tools, a massive routing system (which have simulations in their dark and twisted hearts), air traffic control (which have simulations in their dark and twisted hearts), or something else with a simulation in its dark and twisted heart... chances are that you "business logic" is nothing more than a reporting system with very little behavior about the stuff stashed in the database (or filesystem, or whatever).

    Recognize that the user interface complexity is usually an order of magnitude more complex then the complexity in the problem domain. Don't brush it under the carpet in favor of getting a perfect model of the problem domain. Usually modeling the business problem is, in fact, much easier than modeling the sophistication that eventually gets kludged into the interface facing the humans.

2 writebacks [/src] permanent link