Thursday, April 28, 2005

Stateful services in IoC Containers

Do HiveMind & Co. provide stateful services? I don't think so. Should they? As I'm growing increasingly fond of the Hibernate long-session-pattern, I do think so. You'd probably need some means of attaching/detaching some client-id to a thread (a servlet-filter, typically), so, after that the container could maintain same (service-id, client-id) to service-implementation map. In the servlet-case parallel request from the same client could be blocked (synchronised on) - actually this should be the general case - clients wanting to be served concurrently must provide thread id's instead of client ids.
HiveMind's pooled services should already be quite close, only, the pool needs to become aware of the client-id's -- I need to have a look at them.

Tuesday, April 26, 2005

Intercepting Exceptions in Tapestry Listeners

While I'm growing increasingly fond of Tapestry, there's one tiny advantage to Struts-like Action-classes over listener-methods. Namely, taking care of (non-technical) user exceptions by converting them to friendly messages. A common base-class for all pages overriding AbstractComponent's getListeners() method does the trick. I made it return a sub-class of the standard ListenerMap, which, in turn wraps each of the standard IActionListeners it returns into some custom IActionListener taking care of the standard user exceptions like "ObjectNotFound", "NoPermission", "OptimisticLockBroken" and the like by providing a nice message to a page property (or a delegate-bean).
Still, the way you have to unwrap your exception from the ApplicationRuntimeException is somewhat unsatisfactory. It's just too tight coupling to framework-internals. Maybe, there should be some hook in the framework to plug-in custom exception interceptors into listeners.
A nice way would be this: If the page implements an interface IListenerExceptionListener it would be called whenever a listener throws a suitable exception. Now, suitable could mean:
  • Any checked Exception (declared by the listener method)
  • contained in a list of classes (provided via the listener interface or some other means - could use a class to Listener Map instead, to avoid duplicate switching)
  • Any exception at all.

Wednesday, April 20, 2005

If you can't hide it below, hide it above

In one of the last projects, I was involved in, a Web-Controller accessed business logic through a service facade implemented as a Spring-bean hiding a hibernate-pesisted domain model. Transactions (and hibernate sessions) were controlled declaratively at the service facade. Sounds so nice and clean - nice layers not allowed to know certain things about fellow layers. Only, it didn't work out quite so nice.

  • First: Session/TA-state was, in fact, not hidden below the service facade, because domain objects (detached hibernate entities) were used in the service interface, resulting in plenty of LazyInitializationExceptions and duplicate objects. Tedious DTO's (see Fowler on local DTOs) would have avoided this, but at an unacceptably high price of cumbersome code-duplication.

  • Second: The "natural unit of work" in the application was handling one http request. So, the presentation code had to be careful not to call the service facade twice (or otherwise take over session/ta-control explicitly). So, after all, the presentation layer was, indeed coded, without any knowledge of the technical details below the service facade - sadly it had to know them to work properly.

In my current project, hibernate sessions and transactions are controlled almost entirely from a ServletFilter (See Gavin King's long-session-pattern) - and wow: no more LazyInitializationExceptions - nice "per-request-transactions". And the best of it: presentation and business layer are both freed from doing awkward things pertaining to hibernate persistence (re-attaching detached objects, explicitly setting ta-boundaries, pre-loading lazy collections before handing them back to someone who might need them). It's all hidden above. The only thing left to do is to request a fresh hibernate session at some sensible point (e.g. whenever the user is done with one set of "top-level-objects" and requests a new one).
Ah, yes, so Spring turned out to be rather useless in this particular case ...