Initially I wanted to write a clever piece on extension patterns for modular software systems, but then it got all too long (well… it is tto long), too boring (of course), and too cumbersome (certainly).
But at least I have some reasonably good looking diagrams that I can share with you. So here it goes.
What I wanted to get at with the original concept is that there is a common pattern of software extensibility – the addition of new implementation into a software system at some point in time different than its original definition – that starts in a very simple way (very early on in most systems) but then starts attracting more complex requirements that introduce non-trivial transition costs (and experienced decision making). Just like the modularization topic, this is one that is easily ignored in the beginning, can be worked around for a while but will eventually bite you (at which point the typical decision is to ignore harder as everyone knows it’s going to become expensive).
At least in the early 2000s, before the IoC hype, the prototypical way to implement an extension point, the conceptual or programmatic entity that extensions are associated with in the system, was some class to call to register an instance of an extension (that would typically be required to implement some particular interface):
There is only one advantage to this approach: All you need to know is the place to call. The downsides are significant and all due to its weak definition: All extensions somehow need to get run time, regardless of whether they are actually ever going to be used or not, and there is no way to find extensions much less to control their life cycle in a defined way.
The natural inversion and improvement of this pattern is to let the extension point actively find and load extensions.
This does not only allow to defer loading to the point of time of use, it also allows to be clever about what to load and how to find it. In the wild, a simple, convenient and yet powerful implementation is established by using an IoC container like the Spring framework with lazy-loading and interface-based bean binding. Some more intelligence can be added by requiring custom annotations and configurations.
The limiting factor of this approach is its lack of modularization. All problems discussed in Modularization is more than cutting it into pieces apply. Beyond that, the fact that extension enter the system at some unknown time (from a platform’s perspective) and the expectation of future compatibility make it ever more desirable to avoid conflicts due to sharing one large class loading name space.
So, given a module system (you might guess which one I have in mind here – but I am willing to grant you to think of other ones as well – and after all, the subject as discussed here is sufficiently generic), you need to be able to find and load some implementation from another module. In addition the implementation loaded should be expected to adhere to some API as otherwise the consuming extension point will hardly be able to make sense of it.
From this high, no further qualification of the specifics is possible. However, the extent to which it is possible to intelligently query for extension point implementations and the associated costs are an important consideration. For example, in OSGi, an extender (think the Web application feature) can look into any bundle installed. In turn however, all bundles need to be present and introspected with all their resources on all nodes. In Z2, querying by type and configuration properties can be done without downloading any resources locally. But to benefit from this, all relevant data must be defined statically (which is typically the case). And of course there is no distribution effort, as information is centrally provided by the repository.
Now that we can separate type name spaces into modules and that we can load late, there is two more missing pieces to complete the picture. For once, it should be possible to replace or remove a module and the extension point should respond to that in a meaningful way. This is particularly true at development time. Secondly, whatever is expected to be implemented by the extension may not necessarily be what the consumer can make sense of.
An excellent example for the latter is Web applications. Web applications are a natural extension point. That is, the actual extension is a web application, the whole Servlet API (in the case of Java) the corresponding API, and the consumer is whatever subsystem requires the Web application to be running.
In order to handle that, we should be able to isolate the actual extension point type and its implementation from its consumers and its suppliers – the ones that look for implementations and those that provide them.
OSGi’s Web Application extender is one such case. Z2’s Web App component type works rather similarly. The mechanism of component types is very light-weight and addresses both aspects: Management of runtime dependencies and transformation between component resources and consumer interfaces.
One final practical example can be found here with Z2’s Vaadin addon: Extensions implement Vaadin UI components, e.g. a tab sheet in a multi-tab Web application. The provided components can be queried and consumed by the main Web application. The Vaadin component component type implementation makes sure that an invalidation of an extension, unloads the consuming session so the system stays in a consistent runtime state.