Sunday, September 26, 2010

Extension points (plug-in) design

In a plug-in architecture, extension points allow other users to extend the functionality of your application, usually by implementing interfaces or extending abstract classes (known as the Service Provider Interface or SPI). Designing a good SPI is as important as designing a good API; it should be easy to learn and use, powerful enough to support multiple scenarios and should evolve without breaking existing implementations.

Starting with a simple interface is simple, the problem is keeping it like that as new requirements arrive. For example, take a look at the following MessageSender interface that allow other users to implement different ways of sending messages (e.g. SMS, Email, Twitter, etc.): 

public interface MessageSender {

  void send(Message message) throws Exception;

}

Nice interface; it’s simple and easy to implement. However, suppose that implementations could have an optional name, an optional description and methods to setup and destroy. After adding the required methods to the MessageSender interface, it ends up like this:

public interface MessageSender {

  // clean up resources
  void destroy() throws Exception;

  // return null or "" for empty description
  void getDescription() throws Exception;

  // return null or "" for empty name
  void getName() throws Exception;

  void send(Message message) throws Exception;

  // setup resources
  void setup() throws Exception;

}

Besides breaking existing implementations, this changes will make the interface much harder to implement. So, how can we keep the MessageSender interface as simple as it was before and support the new requirements? Let’s take a look at two different approaches that will keep our SPI simple and extensible: creating optional interfaces and using annotations.

Optional interfaces

Instead of having all those methods in the MessageSender interface, we are going to split them up in three interfaces: MessageSender, Nameable and Configurable.

public interface MessageSender {

  void send(Message message) throws Exception;

}
public interface Nameable {

  String getName();
  String getDescription();

}
public interface Configurable {

  void setup() throws Exception;
  void destroy() throws Exception;

}

Now, we’ve cleaned up the MessageSender interface. Users can implement Nameable and/or Configurable if they need to (they are optional). You’ll have to check at runtime if the optional interfaces are implemented and call the corresponding methods where applicable. For example, the following helper method will call the setup method if the MessageSender implementation implements Configurable:

...
  public static void setup(MessageSender ms) throws Exception {
  
    // check if ms implements Configurable
    if (Configurable.isInstance(ms)) {

      // cast to Configurable and call setup method
      Configurable configurable = (Configurable) ms;
      configurable.setup();
    }
  }
...

Annotations

Another option, besides splitting into multiple interfaces, is to create annotations; for our example, we will need four: @Name, @Description, @SetUp and @Destroy. Implementations can place these annotations as needed. For example, a MessageSender implementation (e.g. SmsMessageSender) with a name “SMS Message Sender” that needs to be destroyed at the end will look like this:

@Name(“SMS Message Sender”)
public void SmsMessageSender implements MessageSender {

  public void sendMessage(Message message) throws Exception {
    // send the message (connect if not connected)
  }

  @Destroy
  public void destroy() throws Exception {
    // disconnect from the SMSC
  }
}

As you can see, we have placed the @Name annotation above the class declaration and the @Destroy annotation in the method that will release the resources. Again, we are going to need some helper methods to check if the implementation has the annotations and call the corresponding methods. For example, the following code will check if the @Destroy method exists and will call it accordingly:

...
  public static void destroy(MessageSender ms) throws Exception {
		
    Method destroyMethod = locateDestroyMethod(ms);
    if (destroyMethod != null) {
      destroyMethod.invoke(ms);
    }
  }
	
  private static Method locateDestroyMethod(MessageSender ms) {
    Method[] methods = ms.getClass().getMethods();
    for (Method method : methods) {
      // check if the @Destroy annotation is present
      if (method.isAnnotationPresent(Destroy.class)) {
        return method;
      }
    }
		
    // no method has the @Destroy annotation
    return null;
  }
...

Annotations gives us more flexibility than optional interfaces as users will only have to add the required annotations. However, both options will make your interfaces simpler, so it’s up to you.

Conclusion

Regardless of the approach you choose, keeping the interfaces (extension points) as clean as possible will allow you to:

  • Lower the learning curve of the interfaces. Users can now focus on the core methods that need to be implemented in order to add new functionality.
  • Make your documentation simpler, especially for the 101 examples. Then, you can create more advanced examples that include more complex scenarios.
  • Evolve the interfaces without breaking existing implementations.

Monday, September 20, 2010

Mokai: Architecture of a Messaging Gateway

Software architecture is all about simplicity. In this post, I’ll show you the process of building the architecture of Mokai, an open source Java Messaging Gateway capable of receiving and routing messages through a variety of mechanisms and protocols including HTTP, Mail, SMPP and JMS (Java Message Service), among others.

The black box

Applications send and receive messages to and from external services such as an SMSC, a SMTP server, a JMS queue, etc. In the picture, we can identify the first three challenges of the gateway:

  • Receiving messages from applications using any protocol.
  • Sending messages to external services using any protocol.
  • Routing messages internally.

Normalizing messages

Let’s start with the last point. How do we route messages internally? Well, with so many protocols for receiving and sending messages, we’ll need a Message class we can manipulate inside Mokai. It will contain some basic information, a body and a map of properties. This way, we can encapsulate any type of information we want inside the message (in the body or properties).

When Mokai receives a message, it will have to be normalized (i.e. encapsulated in a Message object) so it can be routed through the gateway. Once we are ready to send the message to an external service, it will have to be de-normalized (i.e. converted to a protocol that the external service can understand). Easy, right?

Receivers and Processors

The other two challenges we have described above involve extensibility. Anyone should be able to create new protocols for receiving messages or routing them outside the gateway. When creating extension points for your application, make them as simple as you can. In this case, we will introduce two interfaces: Receiver and Processor. Users will have to implement these interfaces to introduce new protocols for receiving or sending messages.

 

The Processor interface is really straightforward. Nothing strange in there. It just exposes two methods; one for checking if the processor supports a Message and other to process it (de-normalizing it to the desire protocol).

The Receiver interface is even simpler; it exposes no methods, but it uses an instance of a MessageProducer to route the received messages inside Mokai. The MessageProducer will be injected automatically to the Receiver at runtime.

Routing messages

When Mokai receives a message from an application, it will have to decide which processor will handle it. To achieve this, each processor will have a collection of acceptors attached. Acceptors are an extension point of the application, so we introduce the Acceptor interface.

The Acceptor interface exposes only one method that returns true if the acceptor accepts the message or false if it rejects it. When a message is received, a router will query each group of acceptors, based on a priority, to select the processor that will process the message.

So far, our architecture looks like this:

We have introduced a new element in the diagram: a router; it’s task is to query all acceptors and processors to see who will handle the message.

Executing actions on messages

It is possible that we might want to execute actions on a message such as validate it, transform it or re-route it to a different location. For this cases, we introduce a new extension point, the Action interface.

The Action interface exposes only one method to execute the action on the message. There are three places where we can execute actions on a message:

  • After a message is received (post-receiving actions).
  • Before the message is processed (pre-processing actions).
  • After the message is processed (post-processing actions).

Now, with the introduction of actions, the complete architecture of Mokai looks like this:

We have encapsulated the receiver in a Receiver Service and the processor in a Processor Service. A Processor Service also has a collection of post-receiving actions not shown in the diagram (processors can act as receivers too). We have also introduced two queues to store the messages until they are processed.

The class diagram

Now that we’ve seen the architecture of Mokai, let’s see the final class diagram that models the architecture.

In this diagram we can see all the elements described in the architecture and their relationships.

Wrapping up

As you can see, Mokai’s architecture is simple, elegant and powerful. It provides all the elements we need to extend the platform to our will, keeping extension points simple and manageable.

In the first release of Mokai, there are also additional services such as message persistence, a plugin mechanism, an administration console and a configuration module, among others. You can check the project homepage for more information.