Introduction

HiveMind is a services and configuration microkernel. HiveMind allows you to create your application using a service oriented architecture.

In HiveMind, a service is an implementation of a Java interface. Unlike other SOAs (such as a SOAP, or EJBs), HiveMind is explicitly about combining Java code within a single JVM. HiveMind uses an XML descriptor to describe different services, their lifecycles, and how they are combined. HiveMind takes care of thread-safe, just-in-time creation of singleton service objects so your code doesn't have to.

HiveMind provides powerful tools for configuring services in a decentralized manner; configuration data can be spread across many HiveMind modules. HiveMind configurations allow for powerful, data-driven solutions which combine seemlessly with the service architecture.

HiveMind allows you to create more complex applications, yet keep the individual pieces (individual services) simple and testable. It offloads all the work of instantiating services, configuring them, and connecting them together. It lets you concentrate on best practices: coding to interfaces, not implementations and designing your code to be easily unit tested.

Status

HiveMind is nearly ready to go into 1.0 beta. To participate, subscribe to the commons-dev@jakarta.apache.org mailing list, a prefix your subject line with [HiveMind].

The project has been reorganized as a Maven multiproject; expect a few teething pains -- especially in terms of pre-packaged distributions. Use Maven to build it yourself! Currently there are two sub-projects; for the framework proper, and for the standard library. A third sub-project, for contributed code and services, will be created soon.

A temporary download location has been set up, but that code is almost always out of date with respect to this documentation. The transition from alpha-2 to alpha-3 is especially painful, because the content of the HiveMind module deployment descriptor changed.

As an early adopter, you should be ready to build the latest snapshot of Maven and use that to build the HiveMind source. We'll try to keep the pre-built distributions up-to date.

Services and Extension Points

HiveMind is a centralized registry of services and configuration points.

Services are represented as a Java interface accessible within a registry using a unique id. Services are usually singletons, though other service models are available.

Configuration points are used for configuration information. Each configuration point is a list of elements, also accessible using a unique id. The elements of a configuration point are provided by contributions that appear in many modules, which is the basis for allowing multiple modules to work together to form the application.

Frequently, services and configuration points are used together; modules contribute to a configuration point and a related service makes use of the data.

Modules

HiveMind doesn't have any particular purpose; it isn't an application, it's an environment for supporting an application.

HiveMind combines together a number of modules to form a registry of services and configurations. Each module consists of:

  • Configuration points
  • Contributions (to configuration points)
  • Service points
  • Implementation contributions (to service points)

Any module may contribute to any other module, including itself.

Each module has a HiveMind deployment descriptor, hivemodule.xml. This defines the services and configurations defined by the module and its contributions to other modules.

Registry

The Registry is the central element of HiveMind. A Registry is always a singleton instance.

The Registry is constructed when the application initializes. All HiveMind module descriptors are located and parsed, and the Registry is constructed from the combined contents. The Registry is validated and any errors are logged.

Once constructed, it is simple to gain access to services or configuration data.

Coding using HiveMind is designed to be as succinct and painless as possible. Since services are, ultimately, simple objects (POJOs -- plain old java objects) within the same JVM, all the complexity of J2EE falls away ... no more JNDI lookups, no more RemoteExceptions, no more home and remote interfaces. Of course, you can still use HiveMind to front your EJBs, in which case the POJO is responsible for performing the JNDI lookup and so forth (which in itself has a lot of value), before forwarding the request to the EJB.

In any case, the code should be short. To access a service:

Registry registry = . . .;
MyService service = (MyService) registry.getService("com.mypackage.MyService", MyService.class);

service.perform(...);
    

You code is responsible for:

  • Obtaining a reference to the Registry singleton
  • Knowing the complete id of the service to access
  • Passing in the interface class

HiveMind is responsible for:

  • Finding the service, creating it as needed
  • Checking the type of the service against your code's expections
  • Operating in a completely thread-safe manner
  • Reporting any errors in a useful, verbose fashion

Documentation

An important part of the HiveMind picture is documentation. Comprehensive documentation about a HiveMind application, HiveDoc, can be automatically generated by the build process. This documentation lists all modules, all extension points (both service and configuration), all contributions (of service constructors, service interceptors and configuration elements) and cross links all extension points to their contributions.

Modules and extension points include a <description> element which is incorporated into the documentation set.

HiveMind is used to construct very complex systems using a large number of small parts. The registry documentation is an important tool for developers to understand and debug the application.

Why should you use HiveMind?

The concept behind HiveMind, and most other microkernels, is to reduce the amount of code in your application and at the same time, make your application more testable. If your applications are like my applications, there is an awful lot of code in place that deals just with creating objects and hooking them together, and reading and processing configuration data.

HiveMind moves virtually all of that logic into the framework, driven by the module deployment descriptors. Inside the descriptor, you describe your services, your configuration data (extension points), and how everything is hooked together within and between modules.

HiveMind can do all the grunt work for you; using HiveMind makes it so that the easiest approach is also the correct approach.

Task Typical Approach HiveMind Approach
Log method entry and exit.
public String myMethod(String param)
{
  if (LOG.isDebugEnabled())
    LOG.debug("myMethod(" + param + ")");
    
  String result = // . . .
  
  if (LOG.isDebugEnabled())
    LOG.debug("myMethod() returns " + result);
    
  return result;
}

This approach doesn't do a good or consistent job when a method has multiple return points. It also creates a many more branch points within the code ... basically, a lot of clutter. Finally, it doesn't report on exceptions thrown from within the method.

Let HiveMind add a logging interceptor to your service. It will consistently log method entry and exit, and log any exceptions thrown by the method.
Reference another service.
private SomeOtherService _otherService;

public String myMethod(String param)
{
  if (_otherService == null)
    _otherService = // Lookup other service . . .

  _otherService.doSomething(. . .);
  
  . . .
}	

How the other service is looked up is specified to the environment; it might be a JNDI lookup for an EJB. For other microkernels, such as Avalon, there will be calls to a specific API.

In addition, this code is not thread-safe; multiple threads could execute it simultaneously, causing unwanted (and possibly destructive) multiple lookups of the other service.

Let HiveMind assign the other service as a property.
private SomeOtherService _otherService;

public void setOtherService(SomeOtherService otherService)
{
  _otherService = otherService;
}

public String myMethod(String param)
{
  _otherService.doSomething(. . .);
  
  . . .
}

HiveMind uses a system of proxies to defer creation of services until actually needed. The proxy object assigned to the otherService property will cause the actual service implementation to be instantiated and configured the first time a service method is invoked ... and all of this is done in a thread-safe manner.

Read configuration data.

Find a properties file or XML file (on the classpath? in the filesystem?) and read it, then write code to intepret the raw data and possibly convert it to Java objects.

The lack of a standard approach means that data-driven solutions are often more trouble than they are worth, leading to code bloat and a loss of flexibility.

Even when XML files are used for configuration, the code that reads the contents is often inefficient, incompletely tested, and lacking the kind of error detection built into HiveMind.

private List _configuration;

public void setConfiguration(List configuration)
{
  _configuration = configuration;
}

public void myMethod()
{
  Iterator i = _configuration.iterator();
  while (i.hasNext())
  {
    MyConfigItem item = (MyConfigItem)i.next();
    
    item.doStuff(. . .);
  }
}	

HiveMind will set the configuration property from a configuration point you specify. The objects in the list are constructed from configuration point contributions and converted, by HiveMind, into objects. As with services, a thread-safe, just-in-time conversion takes place.

The type and number of extension points and how and when your code makes use of them is entirely up to you, configured in the module deployment descriptor.

Testing your Services

HiveMind makes it easier to test your code as well. You service implementations are always simple JavaBeans that implement a service interface you define. You will often have services work together; for example, an OrderProcessing service may make use of an EmailSender service. In HiveMind that looks something like:

public class OrderProcessingImpl implements OrderProcessing
{
  private EmailSender _sender;
  
  public void setEmailSender(EmailSender sender)
  {
    _sender = sender;
  }
  
  public void processOrder(Order order)
  {
      // ... whatever ...
      
    _sender.sendEmailForOrder(order);
  }
}

HiveMind is responsible for instantiating your service implementation, OrderProcessingImpl, and for setting the emailSender property. Notice how clean the code paths are, you don't need to check to see if _sender is null and obtain an instance from somewhere; the property is simply set before your service method, processOrder(), is invoked.

When unit testing, you don't have to use HiveMind to create your services: your unit tests can instantiate OrderProcessingImpl directly, and supply a mock implementation of EmailSender before invoking processOrder().