There simply isn't an application or software system that wouldn't be improved by having a plugin architecture - but it seems harder to implement.
Or at least it seems like it would be harder, in fact it's really a redistribution of labour.
Often when talking about software systems or applications we talk about the core. This is often overused and is an all to convenient metaphor for putting a lot of code together.
Sure, there are times when this is necessary, in fact all systems have to have some sort of core, however the defining line between core and non-core is often blurred to such an extent that the core ends up creeping outwards.
One way to prevent this is to design a plugin architecture from the start - and to continually ask yourself the question whether a set of functionality should be in the core or not.
The biggest mistake that is made when designing a plugin architecture is to start differentiating between the actions that plugins will take. This is wrong as your design starts to be artificially constrained from day 1.
By the very nature plugins provide extensibility - so to pre-classify this extensibility into areas limits what the plugins may perform.
This differentiation is often seemed to be needed to define the interface between the plugin and the core, in terms of method, class structure and data.
How it should be done is by using messaging (events). There are many ways of doing this, however the best suited for a plugin is a Class based inter-object communication.
So the class structure for any plugin becomes much simpler and more flexible:
class GenericExtension implements Receiver { public string getVersion(); public void register(MessageSender msg); public long getRequiredNotificationTypes(); public long getReceiveRate(); public long Receive(Message Msg); }
That's all that the plugin needs to provide. During initialisation the application core will discover (via some mechanism) all available plugins. It will then load and call the register method providing as a parameter the MessageSender that this plugin is associated with. This is necessary to allow the plugin to communicate back to the hosting application.
From this point all of the communications with the plugin are via messages - that will arrive at the receive method - based on the requiredNotificationTypes that the plugin provides.
There can be many different MessageSender instances in a more complicated application - with MessageRouters between them to manage the global message load - however in most systems that are not dealing with large (>10000) amounts of usage this is usually not necessary.
Once the plugin is nolonger required it can notify the host of this - or the host can notify it. In either case once acknowledged with success the host can unload the plugin. If a plugin refuses to respond to an unload request the host is responsible for deciding what to do.
This allows the plugin to function as part of the application - receiving messages and transmitting seamlessly - which is fantastic for things such as filters and processing data.
Direct database access from plugins must be discouraged - unless it is appropriate and it usually isn't. So, again the database access must be via messages - again this may seem slow but it really isn't much overhead - the database messages can be routed directly to the database object. Equally it may be more appropriate to perform access via an API - for example to a set of core business functions. In this case a shared library is more appropriate than overloading the message system.
Using shared libraries with plugins and core code can present a versioning problem so in many ways it should be discouraged - ideally a plugin will be self-contained.
Usually the plugin will require a UI - and here lies another set of pitfalls. If portability of the plugin is important then the UI cannot be contained within the plugin - in which case the only solution is a generic UI (which is fine for web systems), or to use a data driven forms based UI which splits the UI into generic components that are requested by the plugin. This method warrants further explanation, but is outside of the scope here.
So, you have plugins that receive messages, a core system that sends messages and a nicely integrated system. When a new item is plugged in it will fit within this. There are still pitfalls - as plugins may interopate badly with each other - something that only testing and design can solve.
However the goal has been achieved - any number of plugins performing any number of different functions in an extensible way - all simple.
may well need to provide a UI and this is a little more complex than it first seems.