Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Monday, February 23, 2004

Comparing HiveMind to Spring

I've started learning a little bit about Spring ... not just to make a comparison, but to look for proper channels for providing interoperability between HiveMind and Spring. I'm just starting through their documentation right now and will record observations as I go.

My intention here is not to bash Spring or start a flame war. I expect this posting to cause a bit of debate, and a chance for others to point out the inaccuracies that are certain to follow below. The final result will likely be a stand alone document, part of the HiveMind documentation. But you have to start somewhere.

Unlike a lot of open source projects, it looks like they have not ignored the documentation issue.

Yes, Tapestry right now gets bashed for bad documentation. That's because the documentation for 2.3 is actually very good, but the framework has jumped to the next release (3.0) and very little of the documentation has been updated. I've been, ahem, busy.

What follows are my observations as I work through the Spring documentation, starting with the Spring Reference.

Already, I'm trying to adjust to this new term, "Dependency Injection". That's Martin Fowler's term for Inversion of Control. To be honest, I think Mr. Fowler's term is a little short ... yes, the way your objects are connected together is a kind of "injection", but the rest of IoC is the Hollywood Principal (don't call us, we'll call you), which from my point of view is largely about lifecycle support ... when do objects get created and configured, how long are they used for, and how do you keep methods from being invoked under incorrect circumstances.

On the other hand, I'm likely to adopt the term collaborator when discussing different services that work together. Yes, it seems obvious once you've read it!

Back to the comparison. HiveMind's Registry seems pretty close to Spring's BeanFactory (or actually, as I realized later in the document, to Spring's ApplicationContext). They are both the ultimate containers, responsible for creating and configuring services and configurations (in HiveMind) and beans (in Spring). So far, as I'm reading, Spring appears to have different containers used in different contexts. By comparison, for services, HiveMind has different service implementation factories ... such as BuilderFactory and EJBProxyFactory (and, of course, you can roll your own).

This is a difference both minor and significant. Minor, because in the end run, you get to have services of different "flavors" ... are they beans or references to EJBs, or something else? Both HiveMind and Spring can define services (or beans). As I dig through the Spring documentation, I'll see just how well having this split works in practice ... for the moment, I think I'm on the right track with HiveMind to make the services heterogeneous within a single "context": the Registry.

The flip side is that Spring's bean factory is less tied to XML than HiveMind. HiveMind is very, very XML centric ... this is due to its configuration aspect, which is all about XML. And HiveMind is using XML in a very XML way, what with the HiveDoc (conversion of HiveMind module descriptors to a human-readable HTML format via XSLT). Spring has a specific implementation of BeanFactory, XmlBeanFactory, that defines the available beans using an XML format, but in Spring, this is pluggable. This is somewhat pluggable in HiveMind as well, since if you can create a ModuleDescriptor (perhaps from a database or somesuch) you can provide it to the RegistryBuilder for incorporation into the HiveMind Registry. But that is not the core intent of HiveMind ... the core intent is to read XML configuration files from META-INF/hivemodule.xml of all visible JAR files.

Spring can define a bean as a singleton or as a prototype. This roughly maps to HiveMind's service models. HiveMind has a richer set of these and uses a bit more runtime magic.

Spring's <bean> and <property> tags are roughly equivalent to the HiveMind BuilderFactory service. It appears that Spring does a bit more work to simplify property assignments ... for example, it does a bit of analysis of the types of properties to be set and automagically converts the string values (from the XML file) to the right type. I know that approach is more easily accepted by folks like Erik Hatcher. Of course, my attitude is that its always best to start strict and gradually ease up than to start loose and try and inject order.

Beyond that, HiveMind's and Spring's approach to IoC type-2 (set dependencies as properties) and type-3 (set dependencies as constructor arguments) are very close. Not a surprise ... only so many ways to skin a cat. However, Spring borrows a bit from PicoContainer, in that it can attempt to autowire collaborators by name or type. For all that, they're own documentation discourages the use of autowiring.

I wasn't sure what to make of Spring's dependency checking. Does it check to see that all properties of a bean get set? What if the bean's service methods look like properties?

So far, as I read the Spring reference, I can already see a point of difference. Spring will instantiate arbitrary beans. HiveMind is organized around interfaces, so when you define a service, you must also provide the interface for that service. The implementation, core service implementation in HiveMind parlance, is secondary, and may not even be provided in the same module. Advanced builder factories may mean that there isn't a particular class ... it may be brewed up at runtime, perhaps as a reference to something else (such as an EJB stateless session bean).

Spring has lifecycle interfaces that beans may implement to know when they are fully initialized, and when they are being disposed. HiveMind has effectively the same thing, plus an additional lifecycle interface that allows poolable services to know when they are returned to the pool. Spring includes a way, inside the XML, to specify a method to invoke (as an alternative to implementing a lifecycle interface) ... this is not a bad idea; I suspect it exists to support instantiating beans you didn't write, and therefore can't make implement the necessary interface.

In HiveMind, you would generally create a simple "shell" service around the collaborating bean to accomplish the same thing. A little more code, perhaps, but generally when you wrap an existing framework (such as Hibernate) as a HiveMind service, you'll define a new interface anyway.

Spring's BeanFactory has a concept of a configurer. The configurer is responsible for post processing the raw input for the BeanFactory (that is, the data parsed from the XML for an XmlBeanFactory). The example usage they give is to read a properties files and do substitutions of Ant-syntax references.

The generality of having multiple (?) configurers is cool, but if that's the only use, it is not as powerful and flexible as HiveMind's symbol sources. Symbol sources fulfill the same role(and even use the same Ant-derived syntax) , but give you a lot of power in setting up a whole list of places that symbol source data can come from at runtime.

Spring's PropertyPlaceholderConfigurer and HiveMind's symbol sources serve the same purpose: that some information needs to be determined at runtime: e-mail addresses, database URLs and so forth.

So far, I'm still confused about the lifecycle of beans relative to the lifecycle of the bean container. Does the bean container instantiate each bean when it starts up or (more likely) on first reference?

One concern I have for Spring with respect to collaborators is chain of creation. If bean A references bean B references bean C, then accessing bean A will result in B and C being created as well. Early HiveMind did the same thing, before I added in alternate service models and the idea of creating a service proxy to control just-in-time service creation.

Further, if C collaborates with A or B, you can get into a reference loop. The Spring bean lifecycle state diagram indicates that beans are created (via default constructor) first, then wired together second. So A, B and C would be created, then B wired to A, C wired to B and possibly A wired to C. Yes, a diagram would be nice here. However, this diagram and the reference to the default constructor doesn't seem to address what happens when using a constructor to inject dependencies on collaborators. It's a bit of a tangled problem, and easy to ignore ... it takes a particular degenerate case to hit a problem, but when that happens in production you are stuck!

Another advantage of the HiveMind approach (using a proxy) relates to co-dependent collaborators when using type-3 IoC. Using Spring or PicoContainer, it may not be possible to accomplish this ... you can't instantiate A by passing B into its constructor when you need an instance of A to pass into B's constructor. Under HiveMind, this is not an issue: A will be constructed with a proxy to B, and B will be constructed with a proxy to A. Order simply doesn't matter.

Ok, so a few of my observations are slightly off; BeanFactory is the bottom layer, and when you add Spring's ApplicationContext, you start to get to something a bit more like HiveMind, where multiple modules can work together.

Spring has, at this context level, I10N support similar to HiveMind's Messages support. It does support different locales simultaneously, whereas in HiveMind, the locale used for all messages is fixed when the Registry is constructed.

Spring's ApplicationContext provides some useful event interfaces; in HiveMind, the same functionality is available in standard services provided in the hivemind module.

Spring has code related to bean manipulation .. accessing named and nested properties. This isn't an issue in HiveMind but will be in Tapestry. Tapestry will continue to use OGNL for these purposes.

Spring works using JavaBeans property editors, so that it can translate property value strings to object values. In HiveMind, you would use translators (on attributes within elements within a schema) to direct how to convert a string to a property value and even do some validation.

Spring AOP appears to be something of a super-set of Tapestry's AOP (service interceptors). Both make use of interceptor proxies. In AOP terms, both weave around advice for object methods by creating a proxy at runtime. Spring can proxy classes whereas HiveMind can only proxy interfaces. It's generally accepted that programming to interfaces, rather than classes, it a better approach. In this area, Spring is giving developers a choice, whereas HiveMind is leading developers by the nose.

HiveMind's interception approach is built around services and service interfaces. The interceptor factory creates a proxy that implements an arbitrary service interface. So instead of saying "apply this advice to this method of this interface" you say "use this service to create an interceptor for this service". In HiveMind, the interceptor proxy will implement all the methods of the service interface. In theory, an interceptor factory could use a set of parameters to control how it is applied to a specific service and service interface. At this time, the capability is there but none of the built-in interceptor factories do that. Going forward, when we develop a Transaction interceptor factory, we'll start doing exactly that.

Originally I thought Spring AOP was about the same as HiveMind service interceptors (I wouldn't call service interceptors AOP, though maybe "AOP-lite"). As I dug further into the documentation, I realized that Spring's AOP is quite a bit more involved, though I suspect it falls short of the features possible with AspectJ.

It is possible to pass properties to a Spring proxy factory bean (the equivalent of a HiveMind interceptor factory service). Again, HiveMind can define a parameters schema, and pass arbitrary XML as the parameters.

HiveMind has the ability to allow multiple modules to contribute interceptors to a given service point. The upside of this is that new capabilities can be added to a service, such as service interface method logging. The downside is that, unlike Spring, we can't rely on the order in which interceptors are specified to be the order in which they should be applied to the service ... we need to add an order attribute for sorting.

Continuing with research; HiveMind has a concept of multiple modules; all modules are peers and can interoperate (contributing to each other). Spring has a container hierarchy, where (for example) a web application context will have child contexts for each servlet. Names of beans are resolved upward, such that the root application context can provide defaults for beans of a given name when not overridden by a servlet configuration. HiveMind has a single level of services (service ids are unique), but using a namespace approach (the qualified id for a service incorporates the unique id for the module ... thus services with fully qualified ids such as hivemind.BuilderFactory or com. myco.myapp.BusinessLogic).

Remember that HiveMind is a services and configuration microkernel. This is the raison d'etre of HiveMind: complementing and integrating a service model with a distributed configuration model ( inspired by the Eclipse plugin model).

Summary:

Concept Spring HiveMind
Inversion of Control (Dependency Injection) Types 2 and 3
(Property setting and by constructor).
Can autowire.
Types 2 and 3. Explicit wiring only.
Multiple modules Yes, hierarchical. An application context may have children. Yes, flat but namespace qualified.
Aspect Oriented Programming Very rich; standards based. Built in ways to target particular methods. Limited to "around" method introductions on interfaces.
Service management Manages singleton beans (or one-shot "prototype" beans). Bean implementation must be provided with bean definition. Many service models, including singleton, threaded and pooled. Service implementations must implement service interface. Service model hidden behind a proxy object, which may be shared between threads. Service implementation may be contributed by different module that service-point definition.
Configuration information Can configure beans only. Bean definition is single place (single XML file). Very rich; configuration-point to compliment service-point. Supports multiple contributions from many modules. Conversion of XML contributions into Java objects. Proxies for just-in-time access and conversion.
Project Documentation Print out the XML? HiveDoc: rich HTML (like Javadoc) describing all services and contributions across all modules.
Integration Integration with Hibernate, JTA, Spring MVC, more? Planned integration with Tapestry and others -- but nothing yet!

No comments: