A plugin based architecture has many advantages. Some of the common ones include
- Extending an application’s functionality without compiling it again
- Adding functionality without requiring access to the original source code.
- Replacing or adding new functionality becomes easy
- Help in organizing large projects
- Help in extending the functionality of the system to unimagined areas. I know this one sounds extreme but believe me it is true. For our framework, plugins were written by third-party library providers whose domain ranged from health-care to natural language processing.
The major components of our plugin system are the following
- Plugin trait – Which all the plugins would extend
- PluginManager Object – Responsible for loading the plugin at the time of instantiation of the framework
- MessageContext trait – All messages would extend this trait. This makes easy to pass a variety of messages to the plugin without restricting it to a few
Let us look at each of these one by one. Our Plugin trait looks like this
As you would notice, the implementation class would be defining a name, it would have an implementation for a performAction method which takes input as a MessageContext. We expect all our plugins to finally return the result with the result method.
A sample implementation of the plugin would look like this
Now, let us look at the Plugin manager
Now, the consumer who needs to use a plugin, would come to the PluginManager and ask for a plugin via the getPlugin() method. If you remember, each plugin defines its name.
When this method is called, the managers checks if the plugin map is empty. If it is then the init is called where we load the plugins into the framework. Loading the plugins is done on the basis of all classes which are extending the
com.vajra.plugin.VajraPlugin trait. For this, we use are using a utility (ClassFinder) provided by Clapper. More details can be found here.
The idea is simple. As soon as we are starting the framework, we should not have to specify the plugins that the framework would be using anywhere in any configuration. The framework should be able to pick up the plugins automatically. In our case, we pick up all the plugins which are implementing our plugin trait i.e. VajraPlugin
The last piece in our puzzle is the message which is passed to the plugin. Since we want to keep this also extensible, instead of passing the message directly, we use a trait MessageContext.
As you would notice we have a number of utility methods here which all the messages would be implementing. We have a large number of methods here because they are relevant to our system. You could have the relevant methods for your system. A typical message which extends the MessageContext would look like this
The main takeaways are that you should definitely have all the plugins extend a trait. The plugin manager is responsible for creation, destruction and maintenance of plugins. The object passed to the plugin should (could) be a trait so that the framework can be extensible.