HiveMind: What's it all about?
HiveMind, one of the newer Jakarta subprojects at The Apache Software Foundation, is described as "a framework for creating applications, not an application, or even an application server, itself." Howard Lewis Ship created HiveMind while working on WebCT's enterprise e-learning product, Vista. Howard also created Tapestry, a very popular web development framework. Tapestry paved the way for component-based web application frameworks and HiveMind is also beginning to make some waves of its own. In fact, the soon-to-be-released version of Tapestry actually uses HiveMind extensively.
HiveMind, one of the newer Jakarta subprojects at The Apache Software Foundation, is described as “a framework for creating applications, not an application, or even an application server, itself.” Howard Lewis Ship created HiveMind while working on WebCT’s enterprise e-learning product, Vista. Howard also created Tapestry, a very popular web development framework. Tapestry paved the way for component-based web application frameworks and HiveMind is also beginning to make some waves of its own. In fact, the soon-to-be-released version of Tapestry actually uses HiveMind extensively. In a recent email, Howard recently explained to me his thoughts about HiveMind as:
My vision for HiveMind is drawn from the workings of a bee hive or ant colony (or, for Star Trek fans, the Borg collective). Each individual member has a very limited job to do, and does its work without concern with what any other member is doing. And yet, a bee hive or ant colony will expand physically, collect food, produce new generations of workers, fight against intruders ... do all the things necessary to survive, as if it were a single living entity (Douglas R. Hofstadter's book "Gödel Escher Bach: An Eternal Golden Braid" entertainingly discusses this concept at length). This directed behavior on the part of the bee hive is emergent behavior: the individual bees of the hive don't have or need an understanding of the greater goals in order to pursue their part; they just react to their environment, and the constant stream of chemical signals that flow about the hive.
Likewise, in HiveMind, we can set to task a good number of simple services, and by letting them collaborate and share information, they can produce the desired application behavior. But at its heart, HiveMind is about the simplicity that comes from breaking complex problems into small pieces -- the heart of Object Oriented development.
As far as an overview, that pretty much sums it up. HiveMind is all about designing systems as small, testable services which interact with one another to make up an application. In this article, we will explore some of the key features of HiveMind by developing a user registration service to be used by an online application.
The Objective
The requirements for the registration service are:
The user registration service will allow the creation of user accounts based upon an email address. The email address used to create an account must be unique within the system. As part of the registration process, an email should be sent to the user’s email address containing a system-generated password for account verification purposes. The user accounts should be stored in a database.
To follow good programming practices, we define an interface for our service.
public interface RegistrationService { public void registerUser( String emailAddress ) throws UserAlreadyExistsException; }
Since our requirements are relatively simple, our service interface also turns out to be quite simple. Here, we allow the creation of a user account using only an email address. If a user account already exists in the system with the supplied email address, we will throw a UserAlreadyExistsException. Now that we’ve defined what our service looks like, we have to let HiveMind know about our service using a module descriptor file (discussed in more detail later). As of now, we only have one service in our HiveMind module, so our descriptor file will look like:
<module id="hivemind.example.article" version="1.0.0"> <service-point id="registrationService" interface="hivemind.example.article.service.RegistrationService"/> </module>
Here, we’ve created a module with id “hivemind.example.article” containing one service with id “registrationService.” Our implementation class will likely need to collaborate with some other classes in order to fulfill all of these requirements. First, since we will be dealing with persistent user account information, we should create a User class to contain the data. The User class should contain properties for the email address and password (should be somewhat easy to imagine, so we won’t list the code). Next, we will need some way of asking if a user account with the requested email address is already in the database. We will also need to be able to add a user account to the database. These actions logically belong together, as they are both database-related and pertain to the User class.
public interface UserRepository { public User getUserByEmailAddress( String emailAddress ); public void addUser( User user ); }
We should also provide a mechanism for creating new User objects.
public interface UserFactory { public User createUser( String emailAddress ); }
Finally, we will need a way to send account verification emails to our users once their account is created.
public interface EmailService { public void sendEmail( String to, String subject, String body ); }
We now have three more services to tell HiveMind about. Our module descriptor now looks like:
<module id="hivemind.example.article" version="1.0.0"> <service-point id="registrationService" interface="hivemind.example.article.service.RegistrationService"/> <service-point id="emailService" interface="hivemind.example.article.service.EmailService"/> <service-point id="userFactory" interface="hivemind.example.article.domain.factory.UserFactory"/> <service-point id="userRepository" interface="hivemind.example.article.domain.repository.UserRepository"/> </module>
Now that we’ve created abstractions for most the auxiliary actions we need to perform, we can get down to writing the core of our service implementation. With these abstractions in place, the logic is quite simple.
public class RegistrationServiceImpl implements RegistrationService { private UserRepository userRepository; private UserFactory userFactory; private EmailService emailService; public void registerUser(String emailAddress) throws UserAlreadyExistsException { if(userRepository.getUserByEmailAddress(emailAddress) == null) { User user = userFactory.createUser(emailAddress); userRepository.addUser(user); emailService.sendEmail(emailAddress, "Account Registration", "Your new account password is "" + user.getPassword() + ""."); } else { throw new UserAlreadyExistsException(); } } public void setUserFactory(UserFactory userFactory) { this.userFactory = userFactory; } public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } public void setEmailService(EmailService emailService) { this.emailService = emailService; } }
Notice how we are relying upon the userRepository, userFactory, and emailService member variables to be non-null. Many of you will find this to be quite disturbing, because your years of experience have taught you that this will likely cause a NullPointerException at some point. Just relax. We’re going to let HiveMind provide our dependencies for us.
Dependency Injection
When it comes to collaborating with other objects, we have a few choices of how we obtain the references to those objects. We can create the objects ourselves. We can lookup the objects in some sort of repository or registry. Or, we can just have them handed to us by some external entity. HiveMind uses the latter approach and the concept is known as “dependency injection” (a.k.a. Inversion of Control). Dependency injection can be described using “The Hollywood Principle,” or the notion of “don’t call us; we’ll call you.” In our example, we’re letting HiveMind provide us with our dependencies by exposing “setter” methods for them or by using “setter-based injection.” Dependencies can also be injected via constructor parameters and that is known as “constructor-based injection.” With HiveMind setter-based injection and constructor-based injection are not mutually exclusive. You are free to mix and match them as you see fit. Most people stick with just one type for consistency; which type you choose is up to you.
Using dependency injection provides many benefits. One of which is the ability to unit test your implementation very easily. Since our objects aren’t creating the dependencies they need, we have control over what dependent objects they use. We can supply them with “mock objects” which behave precisely as we need in order to test certain conditions. The accompanying code contains JUnit test cases which use JMock to do just that. For now, though, let’s see how to tell HiveMind about our implementation class.
<module id="hivemind.example.article" version="1.0.0"> <service-point id="registrationService" interface="hivemind.example.article.service.RegistrationService"/> <service-point id="emailService" interface="hivemind.example.article.service.EmailService"/> <service-point id="userFactory" interface="hivemind.example.article.domain.factory.UserFactory"/> <service-point id="userRepository" interface="hivemind.example.article.domain.repository.UserRepository"/> <implementation service-id="registrationService"> <invoke-factory> <construct class="hivemind.example.article.service.impl.RegistrationServiceImpl"/> </invoke-factory> </implementation> </module>
Here, we’ve instructed HiveMind that our implementation class is to be used for the “registrationService” service point’s implementation. Since there are service points which implement the interfaces for all of our dependencies, they will be automatically injected. Note, however, that the dependencies can only be “autowired” if there is no ambiguity about which service point to use (outside the scope of this article). So far, we haven’t implemented the other three services in our module. The email service would be fairly easy to write using JavaMail, so we won’t include it. The user factory implementation would be responsible for instantiating a User object and setting its email address and password (randomly generated of course). That, as you can imagine, would be quite trivial also. The user repository, however, will require a bit more effort. Remember that the user repository is an abstraction for the database that contains the user data. We could develop the code using JDBC, but writing object/relational mapping code by hand can be quite tedious. So, we’ll use Hibernate instead. We could have just as easily used JDO or some other O/R mapping tool, but I’m more familiar with Hibernate. Our Hibernate implementation of the UserRepository interface might look like:
public class HibernateUserRepository implements UserRepository { private Session session; public User getUserByEmailAddress( String emailAddress ) { String hql = "select u from User u where u.emailAddress = ?"; Query query = session.createQuery( hql ); query.setString( 0, emailAddress ); return ( User )query.uniqueResult(); } public void addUser( User user ) { session.save( user ); } public void setSession( Session session ) { this.session = session; } }
Again, we must inform HiveMind about our implementation class:
<module id="hivemind.example.article" version="1.0.0"> <service-point id="registrationService" interface="hivemind.example.article.service.RegistrationService"/> <service-point id="emailService" interface="hivemind.example.article.service.EmailService"/> <service-point id="userFactory" interface="hivemind.example.article.domain.factory.UserFactory"/> <service-point id="userRepository" interface="hivemind.example.article.domain.repository.UserRepository"/> <service-point id="hibernateSession" interface="org.hibernate.Session"/> <implementation service-id="registrationService"> <invoke-factory> <construct class="hivemind.example.article.service.impl.RegistrationServiceImpl"/> </invoke-factory> </implementation> <implementation service-id="userRepository"> <invoke-factory> <construct class="hivemind.example.article.domain.repository. impl.HibernateUserRepository"/> </invoke-factory> </implementation> </module>
Notice, however, that we have a service point “hibernateSession” of type org.hibernate.Session (we’re using Hibernate3 so that we don’t have to catch all of the checked exceptions) which has no implementation defined. We can’t just tell HiveMind to construct an object which implements the Session interface directly. Hibernate requires that you use a Hibernate SessionFactory to create Session objects. So, how do we tell HiveMind that it needs to construct a Configuration object, which can construct a SessionFactory object, which can construct a Session object? We must define a “service implementation factory.”
Service Implementation Factories
A service implementation factory is responsible for constructing the implementation object for a service. You may not have known it, but we were using a service implementation factory already when we defined our services’ implementations. When we used the <invoke-factory> element in our module descriptor, HiveMind used its built-in BuilderFactory service implementation factory, which is responsible for performing the dependency injection and various other tasks, to construct our implementation object. If you wish to use a different service implementation factory, you must provide the service id of a service which implements the ServiceImplementationFactory interface. Here’s what our Hibernate Session factory looks like (we’re using the HiveMind 1.1 API here):
public class HibernateSessionFactory implements ServiceImplementationFactory, RegistryShutdownListener { private SessionFactory sessionFactory; private ThreadEventNotifier threadEventNotifier; private boolean updateSchema = true; private Log log; public void init() { log.debug( "Initializing Hibernate SessionFactory..." ); Configuration config = new Configuration(); config.configure(); if( updateSchema ) { log.debug( "Updating database schema..." ); new SchemaUpdate( config ).execute( true, true ); } sessionFactory = config.buildSessionFactory(); } public Object createCoreServiceImplementation ( ServiceImplementationFactoryParameters params ) { log.debug( "Creating Hibernate Session..." ); Session session = sessionFactory.openSession(); threadEventNotifier.addThreadCleanupListener(new SessionCloser(session)); return session; } public void registryDidShutdown() { log.debug( "Closing Hibernate SessionFactory..." ); sessionFactory.close(); } public void setThreadEventNotifier(ThreadEventNotifier notifier) { this.threadEventNotifier = notifier; } public void setLog( Log log ) { this.log = log; } public void setUpdateSchema( boolean updateSchema ) { this.updateSchema = updateSchema; } private class SessionCloser implements ThreadCleanupListener { private final Session session; public SessionCloser( Session session ) { this.session = session; } public void threadDidCleanup() { log.debug( "Closing Hibernate Session..." ); session.close(); threadEventNotifier.removeThreadCleanupListener( this ); } } }
There’s a lot here, so we’ll just go through the methods one-by-one. First, the init() method is used to initialize the Hibernate SessionFactory, using only the default “hibernate.cfg.xml” file (and optionally updating the schema). Then, there’s the createCoreServiceImplementation() method. That method (from the ServiceImplementationFactory interface) is responsible for creating the actual Hibernate Session object. Notice that it registers a listener object with the ThreadEventNotifier which closes the Hibernate Session upon “thread cleanup” (we’ll talk more about this a bit later). Then, we have the registryDidShutdown() method, which is from the RegistryShutdownListener interface. That method will close out the SessionFactory when the HiveMind registry is shutdown (more on this later also). Now that we have implemented a service implementation factory for Hibernate Sessions, we have to let HiveMind know about it:
<module id="hivemind.example.article" version="1.0.0"> <service-point id="registrationService" interface="hivemind.example.article.service.RegistrationService"/> <service-point id="emailService" interface="hivemind.example.article.service.EmailService"/> <service-point id="userFactory" interface="hivemind.example.article.domain.factory.UserFactory"/> <service-point id="userRepository" interface="hivemind.example.article.domain.repository.UserRepository"/> <service-point id="hibernateSession" interface="org.hibernate.Session"/> <service-point id="hibernateSessionFactory" interface="org.apache.hivemind.ServiceImplementationFactory"/> <implementation service-id="registrationService"> <invoke-factory> <construct class="hivemind.example.article.service.impl.RegistrationServiceImpl"/> </invoke-factory> </implementation> <implementation service-id="userRepository"> <invoke-factory> <construct class="hivemind.example.article.domain.repository.impl.HibernateUserRepository"/> </invoke-factory> </implementation> <implementation service-id="hibernateSessionFactory"> <invoke-factory> <construct class="hivemind.example.article.hibernate.HibernateSessionFactory" initialize-method="init"/> </invoke-factory> </implementation> </module>
One restriction placed upon us by Hibernate is that the Session objects are not thread-safe. Therefore, we should only be using one Session per thread. How do we tell HiveMind that it is supposed to instantiate a new Session for each thread? We use a different “service lifecycle model.”
Service Lifecycle Models
HiveMind introduces a somewhat unique concept called service lifecycle models. A service lifecycle model controls when a service’s implementation (the interceptor stack, core implementation object, and all its dependencies) is created. Furthermore, a service lifecycle model can also control which implementation object is used for a specific invocation. The registry delegates to a service point’s service lifecycle model when it needs to return the service object requested by a client. Many service lifecycle models (all except primitive) actually return lightweight proxy objects which implement the service interface rather than the actual implementation. At runtime, when a method is called on the lightweight proxy, it then constructs the implementation and delegates all invocations to it. Let’s take a look at each of the service lifecycle models:
- Primitive – the simplest service model. The service implementation is constructed upon first reference (when the registry is asked for it) and destroyed upon registry shutdown.
- Singleton – the default service model. The service implementation is constructed upon first invocation and destroyed upon registry shutdown.
- Threaded – the service is constructed upon first invocation and to be used only within the calling thread and is destroyed upon thread cleanup. A service implementation class may optionally implement the Discardable interface to receive notifications of when it is discarded by the threaded service model.
- Pooled – the service is obtained from a pool upon first invocation and returned to the pool up on thread cleanup. Optionally, the service implementation class may implement the PoolManageable interface to be receive notifications of when it is activated (bound to a thread) or deactivated (unbound from a thread and returned to the pool).
You’re probably wondering what was meant by “thread cleanup” from the description of the threaded and pooled service models. HiveMind is specifically targeted for web applications and other multi-threaded environments. HiveMind can allow specific services to hold thread-specific state (for the duration of a single web request, typically) and needs to be informed when to free that state data (that is, and the end of the web request). To inform these services that the thread-specific state needs to be freed, HiveMind provides the ThreadEventNotifier service, which notifies listeners of “thread cleanup” events. A thread cleanup event means that the current thread is effectively terminating its execution and any thread-specific information should be cleaned up. The threaded and pooled service models register themselves with the ThreadEventNotifier as ThreadCleanupListeners, so that they may discard or return to the pool the service implementations, respectively. How does the ThreadEventNotifier know when to fire the thread cleanup events? The registry contains a convenience method called cleanupThread(), which tells the ThreadEventNotifier to fire the events. In web applications, the logical time to call cleanupThread() is right before the response is returned to the client. HiveMind comes with a servlet filter which does exactly that. For stand-alone , multi-threaded applications, it’s a little more difficult to figure out how and when to call cleanupThread(). Ideally, you would have a class responsible for executing logic (Runnable objects) in other threads (JDK 5 calls these Executors). That class would be responsible for calling cleanupThread() after each execution. Another lifecycle event, which we’ve already seen, is fired when the HiveMind registry is shutdown. All RegistryShutdownListeners registered with the ShutdownCoordinator will recive notifications. Now that you know how service models work, you’re probably wondering how to use them. That turns out to be the easy part. You specify which service model you wish to use in the <invoke-factory> element:
<module id="hivemind.example.article" version="1.0.0"> <service-point id="registrationService" interface="hivemind.example.article.service.RegistrationService"/> <service-point id="emailService" interface="hivemind.example.article.service.EmailService"/> <service-point id="userFactory" interface="hivemind.example.article.domain.factory.UserFactory"/> <service-point id="userRepository" interface="hivemind.example.article.domain.repository.UserRepository"/> <service-point id="hibernateSession" interface="org.hibernate.Session"/> <service-point id="hibernateSessionFactory" interface="org.apache.hivemind.ServiceImplementationFactory"/> <implementation service-id="registrationService"> <invoke-factory> <construct class="hivemind.example.article.service.impl.RegistrationServiceImpl"/> </invoke-factory> </implementation> <implementation service-id="userRepository"> <invoke-factory> <construct class="hivemind.example.article.domain.repository. impl.HibernateUserRepository"/> </invoke-factory> </implementation> <implementation service-id="hibernateSessionFactory"> <invoke-factory> <construct class="hivemind.example.article.hibernate.HibernateSessionFactory" initialize-method="init"/> </invoke-factory> </implementation> <implementation service-id="hibernateSession"> <invoke-factory service-id="hibernateSessionFactory" model="threaded"/> </implementation> </module>
Notice that we also specified the service id of our previously-defined Hibernate Session factory. Also, Since Hibernate Session objects are lightweight and not designed to be re-used we chose to use the “threaded” service lifecycle model as opposed to the “pooled” service lifecycle model. We have omitted a key component from our implementation, transaction control - but we don’t want to insert transaction control logic into our code. We would rather treat transactions as a “cross-cutting concern” to borrow a term from the aspect-oriented programming (AOP). We can do so using service interceptors.
Service Interceptors
One of the most fundamental forms of AOP is “around advice” where logic is inserted before and after a method call. HiveMind supports this concept using service interceptors. Implementing your own service interceptors can be quite involved, as it requires you to dynamically generate entirely new classes at runtime which implement the service interfaces you wish to intercept (currently done using Javassist or JDK proxies). However, with HiveMind 1.1, support has been added for the MethodInterceptor interface from the AOP Alliance project which simplifies the process tremendously (it also opens the door for reusing outside code…say, the large code base that already exists inside the Spring framework). We could write our transaction interceptor as follows:
public class TransactionInterceptor implements MethodInterceptor { private TransactionService transactionService; public Object invoke( MethodInvocation methodInvocation ) throws Throwable { if( transactionService.isActive() ) { return proceedWithInvocation( methodInvocation ); } else { try { transactionService.begin(); return proceedWithInvocation( methodInvocation ); } finally { if( transactionService.isRollbackOnly() ) { transactionService.rollback(); } else { transactionService.commit(); } } } } private Object proceedWithInvocation( MethodInvocation methodInvocation ) throws Throwable { try { return methodInvocation.proceed(); } catch( RuntimeException e ) { transactionService.setRollbackOnly(); throw e; } } public void setTransactionService( TransactionService transactionService ) { this.transactionService = transactionService; } }
This interceptor depends upon a TransactionService object to provide the actual transaction support. Implementing a TransactionService using Hibernate is quite easy, so I will not include the code here. Abstracting the transactional logic this way allows us to easily switch to another technology in the future. Our transaction interceptor logic would not have to change at all. Also note that it automatically marks the transaction for rollback upon any RuntimeException (a la EJB). With this final piece to the puzzle in place, our module descriptor now looks like:
<module id="hivemind.example.article" version="1.0.0"> <service-point id="registrationService" interface="hivemind.example.article.service.RegistrationService"/> <service-point id="emailService" interface="hivemind.example.article.service.EmailService"/> <service-point id="userFactory" interface="hivemind.example.article.domain.factory.UserFactory"/> <service-point id="userRepository" interface="hivemind.example.article.domain.repository.UserRepository"/> <service-point id="hibernateSession" interface="org.hibernate.Session"/> <service-point id="hibernateSessionFactory" interface="org.apache.hivemind.ServiceImplementationFactory"/> <service-point id="transactionService" interface="hivemind.example.article.service.TransactionService"/> <service-point id="transactionInterceptor" interface="org.aopalliance.intercept.MethodInterceptor"/> <implementation service-id="registrationService"> <invoke-factory> <construct class="hivemind.example.article.service.impl.RegistrationServiceImpl"/> </invoke-factory> <interceptor service-id="hivemind.lib.MethodInterceptorFactory" name="transaction"> <impl object="service:transactionInterceptor"/> </interceptor> </implementation> <implementation service-id="userRepository"> <invoke-factory> <construct class="hivemind.example.article.domain.repository. impl.HibernateUserRepository"/> </invoke-factory> </implementation> <implementation service-id="hibernateSessionFactory"> <invoke-factory> <construct class="hivemind.example.article.hibernate.HibernateSessionFactory" initialize-method="init"/> </invoke-factory> </implementation> <implementation service-id="hibernateSession"> <invoke-factory service-id="hibernateSessionFactory" model="threaded"/> </implementation> <implementation service-id="transactionService"> <invoke-factory model="threaded"> <construct class="hivemind.example.article.hibernate. HibernateTransactionService"/> </invoke-factory> </implementation> <implementation service-id="transactionInterceptor"> <invoke-factory> <construct class="hivemind.example.article.interceptor. TransactionInterceptor"/> </invoke-factory> </implementation> </module>
Notice the insertion of the <interceptor> element inside our <implementation> element for the “registrationService.” This instructs HiveMind to add a service interceptor to the service using the “MethodInterceptorFactory” which is included with HiveMind. Also note the use of the “service:transactionInterceptor” syntax. Here, we’re using the built-in service “object provider” (object providers are outside the scope of this article) to tell HiveMind that it is to use the “transactionInterceptor” service object for the implementation of our service interceptor. Now that we have our module completely fleshed out, we can write a simple application which uses our registration service.
Running the Example
Here’s the complete code for our sample application:
public class Main { public static void main( String[] args ) throws Exception { Registry registry = RegistryBuilder.constructDefaultRegistry(); RegistrationService registrationService = (RegistrationService)registry.getService( RegistrationService.class ); try { registrationService.registerUser( "user@localhost" ); } finally { registry.cleanupThread(); registry.shutdown(); } } }
The first thing we do in our application is create a HiveMind registry object. A HiveMind registry contains zero or more modules. In this case, all of the modules in the registry are described by classpath resources named “/META-INF/hivemodule.xml” (the default location for a HiveMind module descriptor). This is a very important and powerful feature! With this one line of code, HiveMind has automatically discovered and parsed every module descriptor on the classpath and loaded the corresponding modules into the registry. So, if this were a web application having one hundred jar files in the “/WEB-INF/lib” directory, containing module descriptors, there would be one hundred modules in the resulting registry. Again, this was done using only one line of code. Because this mechanism leverages the Java class loader, it is very flexible. If you repackage your web application with additional libraries containing HiveMind module descriptors, they will simply be integrated into the Registry the next time the application is started, without changing any existing code. As you can imagine, this makes assembling HiveMind-based applications very easy. If need be, a RegistryBuilder instance can be used to create custom (non-default) registries. In HiveMind 1.1, there’s even a way to use Groovy scripts to define your modules! However, for the most part, only the default registry will be needed. Next, we used the registry to lookup our service object for the registration service. HiveMind requires that you provide the interface you are expecting so that it can ensure you’re going to get what you ask for. From that point on, we can invoke any of the service object’s methods as usual. That’s all there is to it! With those few lines of code, all of the dependencies were tied together for us and our services were ready to use.
Conclusion
We have perused many of the key features of HiveMind in some detail. In some respects, though, we have barely scratched the surface of what HiveMind can do. For instance, as you experiment with HiveMind, you’re bound to make some mistakes in your module descriptor documents (I made plenty while writing this article). HiveMind, fortunately, contains a feature known as “line-precise error reporting” which can help you quickly identify your mistake. HiveMind also contains build-in support for localization. Another notable feature not covered here involves even listener registrations. HiveMind can automatically “inject” implementation objects (not services) as event listeners into other services. Perhaps the biggest omission is a discussion about configuration points, which allow you to dynamically define XML syntax for passing configuration information into your services.
HiveMind boasts a thriving community of interested users and developers alike. At this moment, the development team is diligently working on the 1.1-alpha-3 release, with a full 1.1 release anticipated soon. With so much involvement, hopefully many more projects like HiveTranse, a SourceForge project aimed at providing a standardized transaction framework for HiveMind, will start to emerge. What is needed is for many projects to adopt HiveMind and create reusable modules and frameworks that others can plug in to their applications. That is the vision of HiveMind, assembling large applications from small, testable, reusable components.
About the Author
James Carman an independent software consultant from Cincinnati, OH. He is one of the directors of our local Cincinnati Java Users Group (www.cinjug.org). As of late, he has been working in Bioinformatics, but in the past has done more business-oriented programming. He is also a committer on the Jakarta Commons and Jakarta HiveMind projects at The Apache Software Foundation.