Thursday, June 22, 2006

Injecting Spring managed beans into IChannels

Someone wrote me to ask my thoughts on injecting Spring-managed beans into IChannels. This is what I had to say.

I've been playing with using your PortalApplicationContextFacade to acquire Singleton Beanfactory references to acquire Spring Managed beans implementing middle tier operations into my channels. Thoughts?

It's an approach. Channels are tightly coupled to uPortal anyway, so being tightly coupled to the context façade and looking up magic beans by name isn't the end of the world. This approach has the advantage of feeling a lot like our crufty static services invocation approach to life -- the Lookup (anti-)pattern.


Is there a better way?


I'm very attracted to going a step further and using ChannelToBeanProxy where the proxy IChannel looks up a named IChannel bean from the PortalApplicationContextFacade. Then you can wire in your Spring-managed beans to the channel, potentially wire in different service realizations or different proxies on top of services into different channels, etc. Gets a little closer to dependency injection.


Whether you inject into a Spring-managed IChannel instance or you write an IChannel that looks up the beanfactory and beans it needs probably isn't that different, though.


I've been digging for a way to get a startup contextListener to bootstrap the singleton beanfactory and maybe use the factory facilities to have it manufacture beans from the same context in both Spring managed and static contexts, but just thinking about it is so ugly...


It's not that ugly, and we really need to get there. Step one was what we did. Next step is to really buy Spring, use a Spring-provided context listener to bootstrap, and then use a uPortal-provided secondary context listener to expose that Spring-created application context behind the PortalApplicationContextFacade, and this *just doesn't work* outside the context of a running uPortal in a servlet container. Let's get firm about that: that what we're writing is objects and services that run in a running portal, and anything else (Ant tasks?) doesn't belong in the same source tree and can't use these services that legitimately only exist in a running portal except by remoting to the portal. And then go after building and wiring and configuring the services that those components will use -- I don't object to their existing, but it's a serious problem that they basically "happen to work" in the current implementation based on classpath magic and rdbm.properties happening to be in the right place. So, concretely, if you declare a Spring bean that depends on JNDI, and an ant task runs and touches something that touches the PortalApplicationContextFacade, it blows up because the JNDI lookup fails outside the context of the portal running in a container. Which exposes a deeper design problem of running portals and outside the portal tasks trying to use the same services and configuration. A recipe for complexity.

So we drop the requirement of running outside a servlet container. We re-approach those problems and come up with things that intentionally work outside the container, either by having their own configuration or their remoting to a live portal or their using services explicitly designed for that context or whatever it's going to be. Write the uPortal-user-provisioning-tool-as-Eclipse-plugin. But if it touches PersonDirectory, have it do so be deliberately and be very sure how that is going to be configured is worked out.

In the servlet container, we embrace being in the servlet container. Install and rely on that context listener from Spring.

Okay. That was step two.



But we really need to get to step three, which is not only using the Spring context listener, but also using Spring WebMVC. Slam everything we got into a couple controllers, fine. But open up that potential for adding more parallel controllers, for starting to use Spring WebMVC error handling, slapping interceptors and aspects in.

Peter's rendering pipeline plugability and so forth for uP3 is great. But I want it crunched into a Spring controller.



Now I'm out of steps and just observing:

Firstly, uPortal's channel manager really wants to be a Spring bean container. ChannelToBeanProxy is cute and I'm convinced it's useful, but I think it just exposes a deeper realization that ChannelManager should be implemented in terms of a bean container. Channels are beans. The get their configuration wired in. But get them from a bean container that's able to differentiate prototypes from singletons and wire in richer dependencies and apply aspects and so forth. What we've proven with all those static factories and portal.properties properties is that we need to configurably wire services into channels.

Has anyone written the RBDMS-backed bean container?



Second, as cool as it is to wire services into channels, wouldn't you much rather be writing a portlet and use Spring PortletMVC? Wiring dependencies into channels only gets you so far, you're still stuck with the lack of compelling MVC framework and pluggable view technologies for channels. So we keep reinventing it. CWebmail has IXmlMethod. Other channels do other things. Over and over we re-implement grabbing parameters from static data and runtime data. I've kicked around the idea of writing Spring uPortalChannelMvc, which would look a whole lot like Spring PortletMVC except work for channels, binding runtime data to command objects, doing the validation thing, bringing in pluggable vie technology. My thought was that it would be great fun and ultimately low value because I should just give up and write Spring PortletMVC portlets.



The most important, transformative feature of uPortal 3 is pervasive Springiness, and while you can be better or worse at teasing out those extension points and defining good interfaces, more or less you can't help but produce a pluggable architecture if you use Spring properly, and it is this sustainably pluggable architecture that will be the single greatest contribution of uPortal 3. Let's really go after open-closed in the context of uPortal the way Scott has in CAS.

JSR-168 and JSR-286 are uninteresting from the perspective of JA-SIG. They don't matter. Don't go write a JSR-168, and don't worry about what features will be in JSR-286. These standards are the necessary evil that makes frameworks like Spring PortletMVC possible, and once that's possible, the productive thing is to jump on that. What we should all be doing is taking a look at Eric's Bookmarks portlet, articulating what extension points it needs to be an asset to MyRutgers, to Academus, to MyWisc. And then going after the open-closed core that works for everyone and local plugins that make us happy. And that's all about Spring PortletMVC and a domain model and good interfaces, and has very little to do with JSR-168.

JSR-168 is better than IChannel because it gets you to Spring PortletMVC. JSR-286 will be valuable only inasmuch as it allows SpringPortletMVC to be better. I'm interested in what I can do with PortletMVC and the opportunities it gives us to share controllers. I'm not interested in 286 for its own sake since the most productive thing to do is to jump on the PortletMVC framework.


I see the value of injecting spring resources into IChannels as allowing incremental decoupling and Springification of channels, allowing you to demonstrate incremental value or at least progress. The natural outcome is an eventual realization that all that's left in the channel is controllerish stuff, that all the services and domain have been extracted. But then the next step is to move the service and domain into a Spring PortletMVC portlet and re-implement the controller in that richer environment, continuing to realize incremental value in terms of not being forced to use XSLT as the view layer, etc. etc.

1 comment:

Jason Shao said...

I'm actually pretty excited about this - had a chance to read into ACEGI's filter proxy... I'm thinking about taking a crack at it, combined with a real application context and the WebContextUtils locator - means the proxy channel will need to be IPriveleged, but it's kind of infrastructural...