Plug-in Architecture
By: Omar Elgabry
Tags: Software Engineering, Scalability, Software Development, Software Architecture, Thoughts

This is one of the things that we probably use and interact with on a daily basis but seldom do we realize its existence.

Not only does it exist in product-based applications such as Eclipse (IDE) or any browser where it can be customized by adding plugins and extensions, but it also exists in business-based applications where business rules and data processing logic vary according to, say, a country such as in insurance claim and taxes applications.

The underlying idea is simple. Being able to plugin features to an existing component without this component having to know about the implementation details of these plugged-in features.

It sounds like I’ve heard the same sentence before.

Polymorphism (OOP)?. Protected Variations (Design Principle)?. Strategy Pattern (Design Patterns)?.

And yes, that’s right!. They all embrace the same concept.

Description

The plug-in architecture consists of two components: a core systemand plug-in modules.

The main key design here is to allow adding additional features as plugins to the core application, providing extensibility, flexibility, and isolation of application features and customs processing logic.

The specific rules and processing are separate from the core system. At any given point, we can add, remove, and change existing plugins with little or no effect on the rest of the core system or other plug-in modules.

Core system

At a high level, it defines how the system operates and the basic business logic. There is no specific implementation, no customization. It is abstracted.

A simple example. The generic workflow, such as how the data flow inside the application is defined. But, the steps involved inside that workflow is up to plugin. And so, all extending plugins will follow that generic flow providing their customized implementation.

Digging deeper a bit, it also handles special cases, applies special rules, and complex conditional processing. These are the things that need to be enforced regardless of the extending plugin.

In addition, it also contains the common code being used (or has to be used) by multiple plugins as a way to get rid of duplicate and boilerplate code, and have one single source of truth.

For example, if two plugins do log the transactions and failures, the core system should provide such a feature logger as part of it. Not to mention things like security, versioning, UI components, database access, caching, etc.

Plug-ins

The plug-ins are stand-alone, independent components that contain specialized processing, additional features, and custom code that is meant to enhance or extend the core system to produce additional capabilities.

Generally, plug-in modules should be independent of other plug-in modules. Though, some plug-ins require talking to, or assumes the presence of, other plug-ins. Either way, it is important to keep the communication and the dependency between plug-ins as minimal as possible.

Core←→Plug-ins

The core system needs to know about (1) the extending plug-in modules and (2) how to get to them.

The core system declares extension points that plugins can hook into. These extension points, these hooks, often represent the core system life cycle.

And so, each plugin registers itself to the core, passing some information such name, communication protocol, input/output data handlers, data format, and hooks into these extension points.

There should be a well-defined interface between the core and the plugins.

How the core system connects to these plugins is entirely based on the type of the application building (small product or large business application) and your specific needs (e.g., single deploy or distributed deployment).

A quick glimpse over the different ways of connecting any two components.

Configurations

By now you should have already noticed that these plugins need to announce themselves to the core system, tap into the extension points, and pass some information.

That brings the idea of configurations; The glue, point of contract, by which we attach a plugin to the core system, where everything mentioned above (extension points, comm. protocol, etc) is defined.

// This plugin hooks into the core system life cycle

// and saves the daily activities events emitted

// by core into a database. core.registerPlugin({

name: 'track-my-activities',

port: 8081,

hooks: {

wakeup: function (time) {

saveTime("Woke up at: " + time);

},

work: function (time) {

saveTime("Started work at: " + time);

},

exercise: function (time) {

saveTime("Exercising at: " + time);

}

}

});

The configurations not only gives us the agility but also allows us to visualize how each plugin works and perhaps, the flow of the data.

If a glueing a plugin to an existing core system is a pain due to having an incompatible interface, it is common to create an adapter between the plug-in and the core system. And so the core doesn’t need a specialized code for each incompatible plug-in.

Where the configurations can be stored?

The configurations can be either in the code itself, passed to a CLI tool, or stored in the database. It can be written in various languages like YAML, TOML, JS, JSON, XML, etc.

Whatever the language you decide to use, make sure you’re able to do things like comment, conditions, validations, loops, or whatever needed. Clearly, that’s not possible with JSON.

In addition, a small tool can be built to validate these configurations.

One Vs Multiple instances

We’ve mentioned two common use cases. The product-based and business-based applications.