Chapter 10. Making Things Pluggable

I'm a huge fan of plugin architectures. Besides their tremendously positive effect on your application and scope management, they are also a lot of fun to develop. I'd recommend integrating a plugin architecture in their library or application to anyone who asks me. A good plugin architecture allows you to write a concise application core and provide additional functionality via plugins.

Designing your whole application in a way that it allows you to build a plugin architecture has a great effect on the extensibility of your system. This is because you're making your application open for extensibility but closing it for modification.

While authoring my open source projects, I also experienced that a plugin architecture helps you manage the scope of your project. Sometimes, a requested feature is really nice and helpful, but it will still bloat the library core. Instead of bloating your whole application or library with such features, you can simply write a plugin to get the job done.

In this chapter, we will create our own plugin architecture that will help us extend the features of our application without bloating its core. We'll first build the plugin API in the core of our application and then use the API to implement a nice little agile plugin, which helps us to estimate tasks using story points.

We'll cover the following topics in this chapter:

  • Designing a plugin architecture, based on the Angular ecosystem
  • Implementing a decorator-based plugin API
  • Using ComponentResolver and ViewContainerRef to instantiate plugin components into predefined slots in our application
  • Implementing a plugin-loading mechanism using SystemJS
  • Using a reactive approach in our plugin architecture to enable plug and play style plugins
  • Implementing an agile plugin to record story points using the new plugin API

Plugin architecture

At a higher level, a plugin architecture should fulfil at least the following requirements:

  • Extensibility: The main idea behind plugins is to allow the extension of the core functionality using isolated bundles of code. A great plugin architecture allows you to extend the core seamlessly and without noticeable performance losses.
  • Portability: Plugins should be isolated enough so that they can be plugged into the system during runtime. There shouldn't be a necessity to rebuild a system to enable plugins. Ideally, plugins can even be loaded at any time during runtime. They can be deactivated and activated and should not cause the system to run into an inconsistent state.
  • Composability: A plugin system should allow the use of many plugins in parallel and allow an extension of the system by compositing multiple plugins together. Ideally, the system also includes dependency management, plugin version management, and plugin intercommunication.

There are a lot of different approaches on how to implement a plugin architecture. Although these approaches can vary a lot, there's almost always a mechanism in place that provides unified extension points. Without this, it will be hard to extend a system uniformly.

I've worked with some plugin architectures in the past, and besides using existing plugin mechanisms, I've also enjoyed designing some of them myself. The following list should provide an idea about some of the approaches that you can use when designing a plugin system:

  • DSL: Using domain-specific languages is one way to implement a pluggable architecture. After you've implemented the core of your application, you can develop an API or even a scripting language that allows you to develop further features using this DSL. A lot of video game engines and CG applications rely on this approach. Although this approach is very flexible, it can also lead to performance issues quickly, and it's prone to introducing complexity. Mostly, the prerequisites to implement such an architecture are to expose very low-level core operations (such as adding UI elements, configuring process flows, and so on) into the DSL, which does not provide clear boundaries and extension points but is extremely flexible. Some examples of DSL-based plugin systems are most of Adobe's CG applications, 3D Studio Max, and Maya, but also game engines, such as Unreal Engine or the Real Virtuality Engine from Bohemia Interactive Studio.
  • The core is the plugin system: Another approach is to build such a sophisticated plugin architecture that it fulfils all the outlined requirements in the previous listing (extensibility, portability, and composability) and even some more sophisticated requirements on top. The core of your application is one large plugin system. Then, you start to implement everything as a plugin. Even the core concerns of your application will be implemented as plugins. A perfect example of this approach is the Eclipse IDE with its Equinox core. The problem with this approach is that you're likely to run into performance problems as your application grows. As everything is a plugin, optimization is quite tricky, and plugin compatibility can make the application unstable.
  • Event-based extension points: Also, a great way to provide extensibility of a system is by opening up the pipeline of your system to input from outside. Imagine that for every important step in your application, you notify the outside world about the step and allow interception before the application continues with processing. In this manner, a plugin will just be an adapter that listens for these pipeline events of your application and then modifies the behavior as required. A plugin itself can also emit events, which then can be processed by other plugins again. This architecture is really flexible, as it allows you to change the behavior of your core functionality without introducing too much complexity. It's also fairly easy to implement this approach even after you've finished your core without any thoughts about a plugin system. I've been following this approach in my open source project Chartist and, so far, I've had very good results with it.
  • Plugin interfaces: An application can expose a set of interfaces that define certain extension points. This approach is heavily used in the Java framework where it's known as Service Provider Interface (SPI). Providers implement a certain contract, which allows the core system to rely on an interface rather than an implementation. These providers can then be cycled back into the system where they are made available to the framework and other providers. Although this is probably the safest way to provide extensibility in terms of uniformness, it's also the most rigid one. A plugin will never be allowed to do anything else that was specified in the contract of the interfaces.

You can see that all four approaches vary a lot. From the top-most, which provides extreme flexibility at the cost of complexity and stability, to the bottom-most, which is very robust but also rigid.

The approach that you choose when implementing a plugin system heavily depends on the requirements for your application. If you do not plan on building an application that comes bundled in various flavors and where multiple versions for completely different concerns should exist, the approaches to the bottom of the preceding listing are probably more likely the ones that you should follow.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset