Modular Application Architecture - Considerations
When developing software, sometimes we need to allow our application to have plug-ins or modules developed by third parties. In this post we will take a general overview on how some popular design patterns can allow us to create plugin based applications and some considerations to keep in mind when implementing them.
This is the fifth post from a series of posts that will describe strategies to build modular and extensible applications. In this post we will take a general overview on how some popular design patterns and things to keep in mind when creating plugin based applications.
In the previous posts we saw the "Event Manager" (in reality a variation of the Mediator pattern), the "Pipeline" (in reality a variation of Chain of Responsibility pattern) and the use of inheritance to build plugin systems. Many of the plugin systems are just customizations of popular design patterns.
Anthony Ferrara (alias ircmaxell), in this post blogged about the use of software patterns to implement plugin-based architectures. It is a great article and I suggest everybody to read it. As it is clear from the article, each of this software patterns has a specific use case and the choice of which one to use depends on which the of integration we want allow for the future plugins.
Recap
- The Observer and Mediator pattern
allows you to have communication between independent objects.
Is one of the most popular patterns when building plugin-based systems that allows to add or change functionalities. The application has still full control on what plugins are able to do. - Strategy, Decorator and Chain of Responsibility patterns are convenient when one of the plugin should be allowed to change or the behaviour of one functionality or to implement missing functionalities.
- Inheritance combined with the Template method pattern is useful when we want to grant to a plugin full control over the application. The plugin replaces the application code with its code.
Use cases
All of this patterns are extremely powerful and have they specific use cases. Most of them can be also implemented in a not-object-oriented way. As example:
- The Wordpress theme system can be seen as a "Inheritance+Template method" based system
- Drupal and Wordpress use the Mediator pattern to implement most of their plugin systems (most of it was procedural code)
Tips
1) One one fundamental point to make a plugin system effective, is the way how the application allows to register and configure plugins. Most of the power of a good plugin system comes from a good registration and configuration mechanism.
The more a plugin is able to "hook" into the core of the application the more functionalities will be able to provide. On the other hand, the way that the plugin uses to "hook" into the core should not be "hacky", should be simple and should be the same used by the application core to offer its core functionalities.
A good example in my opinion can be the Symfony's bundle system. It uses extension points and service tagging to allow to plugins (bundles) to register their functionalities in the same way used by symfony itself.
2) Another tip is to avoid imposing constraints when not necessary.
Can be tempting to offer at application level
some "standardized" control panel or storage to configure the plugins... please don't.
Sooner or later there will be a case that will be not supported by the "standardized" format you decided.
Most likely you will end up in this scenario
and you will have to edit the "standardized" format you decided.
The application core should to only the minimum necessary.
Conclusion
In this article we saw some use cases for common software design patterns and some other
considerations to keep in mind when building plugin based systems.
A part that is missing from this series of blog posts (and will be covered in the next post) is
how to deal with static assets (as images, css, files).
Hope you enjoyed this article and if you have some feedback, do not hesitate to leave a comment.