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!
|