app-plugins v1.0.0
Application Plugins
Node.js application plugin framework. If you want your application to be extensible and loosely coupled then this is the way to go.
Have you ever needed to write a Node.js application that is extensible? Maybe you
- needed to enable a third party (like contractors) to write code for you without messing around in the main application
- wanted to decouple the work of different teams
- simply needed your codebase to be smaller and more manageable
then this is for you!
About
This module manages the plugins in memory by exposing a PluginManager and ServiceContainer.
The PluginManager interacts with the loaded plugin code, calling load and unload methods to manage the lifecycle and making sure that services are isolated between plugins and the main application so plugins can be unloaded at all times.
The plugin isolation means, that there are always multiple service instances.
- one global instance on the plugin manager
- one instance per plugin
Each instance gets created on first use so not all of those instances are necessarily alive in any given application state.
Having multiple instances allows us to separate plugins cleanly but comes with an extra challenge. When service calls are invoked, they must also check all other service instances. Consider this example of a routing service:
- each plugin registers a route
- when the application code tries to check whether a route is available, it has to check all the service instances since each plugin has registered its route on its own instance of the routing service
- the routing service gets a list of all routing service instances
- it needs to expose a method to communicate with the other instances, in this case to return the registered routes or to check only itself whether a route is available
- once the service instance is found that can handle the request, the loop can be stopped
A function getServices is exposed on the Service base class and helps in managing the other service instances.
Code example:
class RoutingService extends Service {
registerRoute(route: any): void {
...
}
canHandleRequest(request): boolean {
return ...
}
handleRequest(request: Request) {
if (this.canHandleRequest(request)) {
// actually handle the route
return ...
}
// check all service instances which one can handle the route
const serviceList: List<RoutingService> = super.getServices();
const routingService = serviceList.find(service => service.canHandleRequest(request);
return routingService && routingService.handleRequest(request);
}
}
Usage
Create a new instance of the PluginManager. This should be a singleton and manages all plugins in your application. Loading a plugin is a 2-stage process.
- create an instance of Plugin passing the name of your plugin (filename or other unique identifier)
- stage the plugin with the PluginManager calling pluginManager.stage(plugin), this will prepare the ServiceContainer instance specific for the plugin the main reason for this early process is to already give you access to the plugin's service instances before the plugin code has necessarily been loaded this can be used to hook into the Node.js module loading process and supply registered services through the "require" mechanic in Node
load the plugin by calling PluginManager.load(plugin, pluginModule)
import { PluginManager, Plugin } from 'app-plugins';
// create a new PluginManager instance and store it as a singleton in your application const pluginManager = new PluginManager(); pluginManager.registerService('SVC_LOGGER', LogService);
// plugins need to be wrapped in the Plugin class before being staged in the PluginManager const plugin = new Plugin('test-plugin'); pluginManager.stage(plugin);
// the Plugin class exposes the plugin specific service container in case specific services need to be registered for a particular plugin const log = plugin.getService('SVC_LOGGER');
// the PluginManager will call TestPlugin.load const testPlugin = new TestPlugin(); await pluginManager.load(plugin, testPlugin);
// unload the plugin if it is no longer required await pluginManager.unload(plugin);
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago