Michal's Design Study
(12 intermediate revisions by one user not shown) | |||
Line 27: | Line 27: | ||
Mr. U. Ser decides to chat with his friend Wal using GTalk. U. Ser finds Wal in the contact list he can see in Boris. He chooses "GTalk chat" from the menu. Boris tells Calla to call Wal using the username from Wal's information stored in AddressBook. Calla calls Wal and notifies Boris when the call connects, so Boris can display the video feed. When the call disconnects, Calla notifies Boris again. | Mr. U. Ser decides to chat with his friend Wal using GTalk. U. Ser finds Wal in the contact list he can see in Boris. He chooses "GTalk chat" from the menu. Boris tells Calla to call Wal using the username from Wal's information stored in AddressBook. Calla calls Wal and notifies Boris when the call connects, so Boris can display the video feed. When the call disconnects, Calla notifies Boris again. | ||
+ | |||
+ | == Supplement: The XML Plugin Description Format == | ||
+ | [[PluS XML format]] | ||
== First Design - Making it Work == | == First Design - Making it Work == | ||
+ | [[Image:mkc_design1.png]] | ||
+ | |||
+ | === How does this design work? === | ||
+ | '''Core''' finds all the XML files in the plugins folder and throws them into a list. It then goes through each file in turn doing the following: | ||
+ | # A '''PluginDescription''' is created for each plugin | ||
+ | # The information is collected from the '''PluginDescription''' in order to create the '''''Plugin''''' | ||
+ | # The '''''Plugin''''', along with a list of prerequisites and extension points, is registered with the '''PluginRegistry''' | ||
+ | Once all the plugins are registered, registrationComplete() is called, and the '''PluginRegistry''' activates each of the '''''Plugins''''' | ||
+ | |||
+ | The entry point for a Plugin must extend the abstract class '''''Plugin''''' | ||
=== What's right with this design? === | === What's right with this design? === | ||
+ | About the only thing I did vaguely right in this case was using the observer pattern a couple of times. The PluginRegistry is an Observer of each of the Plugins, and Plugins can also observe other Plugins. It made sense at the time. | ||
+ | |||
+ | I like having an enum for return codes rather than just returning an int or boolean. | ||
+ | |||
+ | I like having the XML parsing of the Plugin description distinct from the Plugin itself | ||
=== What's wrong with this design? === | === What's wrong with this design? === | ||
+ | |||
+ | Plenty. This was just a first prototype to see if the idea would even be doable. As such, I didn't put a lot of effort into designing anything at all. | ||
+ | |||
+ | Let's have a look at some [[Antipatterns]], broken [[Design maxims]] and trampled wisdom: | ||
+ | * [[Poltergeists|Poltergeist]] - The PluginDescription is a Poltergeist. Each Description appears and then disappears within a single run-through of a loop in the Core's main() | ||
+ | * [[Encapsulation]] - Core is doing a little more than it should, particularly in the way it handles the instantiation of Plugins. Rather than telling the PluginDescription to use the information contained in it to create a Plugin object, Core gets all the information from PluginDescription and creates the Plugin object itself. It then hands it off to the PluginRegistry. This breaks several of Riel's Heuristics: 2.1:[[Hide data within its class]], 2.9:[[Keep related data and behavior in one place]], 3.3:[[Beware of many accessors]] all relate to encapsulation. Core is also a potential [[God class]] | ||
+ | |||
+ | In other news, Core is never instantiated, and has only a main() and a static field plugins, which is the PluginRegistry. | ||
== Second Design - Making it Right? Maybe Not== | == Second Design - Making it Right? Maybe Not== | ||
+ | [[Image:Mkc_design2.png]] | ||
− | === What's | + | === What's improved with this design? === |
+ | * Core (now PluginLoader) has lost some of its God Class tendencies. In this version it gets the list of files, creates XMLPluginReaders for each file, and tells the XMLPluginReader to register with the PluginRegistry. | ||
+ | * XMLPluginReader has shifted towards [[Tell, don't ask]]. It now gives the information needed to the PluginRegistry when it is asked to register instead of having multiple accessors. | ||
− | === What's wrong with this design? === | + | === What's still wrong with this design? === |
+ | * There is some redundancy in PluginRegistry. There are two Maps indexed by PluginID. There should be a way of reducing that to one. | ||
+ | * XMLPluginReader is still a poltergeist, only existing within a loop within PluginLoader's run() | ||
+ | * Error handling is non-existent | ||
+ | * Extensions are not yet supported | ||
− | == | + | == Lightbulb Moments == |
+ | The first | ||
+ | Extensions don't need to be plugins. Essentially they can just plug straight in to the extension point they are extending. | ||
+ | This was closely followed by the second | ||
+ | Extensions are plugins, just not plugins to the main system. Instead, they're plugins to the extension points. | ||
+ | This soon lead to the third | ||
+ | The main system is really just an extension point. | ||
+ | This sent me back to the drawing board for another redesign | ||
− | == | + | == Third Design - A shift in perspective == |
+ | [[Image:Mkc_design3.png]] | ||
− | === | + | === How does it work? === |
+ | |||
+ | '''PluginLoader''' grabs the list of XML files from the plugins folder. It uses the '''ComponentBuilder''' to create a '''Component''' for each XML description. As each Component is create it is registered with the PluginLoader. | ||
+ | |||
+ | PluginLoader then checks that all the dependencies for each Component are available. If any Component is lacking a dependency it is removed from the set of Components. Currently the algorithm for doing this is very inefficient. Once all dependencies have been checked, the Components are told to instantiate their Plugin, and the PluginLinker connects all the Plugins together. | ||
+ | |||
+ | Each Component contains all the information related to its Plugin. This includes the ID of the host and extension point it connects to, as well as the Plugin's jar file, main class and dependencies. | ||
+ | |||
+ | PluS is the core system. It is essentially just a plugin in its own right, however it doesn't have a host. As they are connected together, the Plugins will naturally form a tree structure, with PluS as the root. | ||
+ | |||
+ | Plugins are responsible for managing their extension points. The registerExtension method takes in the ID of the extension point and the extending Plugin. It is up to the Plugin to connect the extension correctly. | ||
+ | |||
+ | === Thoughts on the design === | ||
+ | |||
+ | So far, this design has managed to stay quite small and unbloated. There aren't a lot of design patterns, which is not surprising given the small scope. | ||
+ | |||
+ | * [[Builder]] - ComponentBuilder is an example of the Builder design pattern. Creating a component involves parsing XML to extract all the relevant information and then using that information to create the Component. Abstracting this process out to a separate class keeps the PluginLoader much cleaner and tidier. | ||
+ | * [[Separation of concerns]] - I think this is better in the current design than the previous iteration. Keeping track of all the details for a particular Plugin has been abstracted out to Component. The information about the Plugin doesn't make sense to be kept inside the Plugin (why should a Plugin have to know what the name of the jar file it's packed in?), but does need to be kept nearby. Having the Component makes this simple. The linking together of Plugins has also been abstracted out to PluginLinker. Once again, this helps to keep PluginLoader cleaner as well as making sure PluginLoader isn't doing more than it should. | ||
+ | * [[Encapsulation]] - Using the Component class created a few issues with encapsulation. To get around some of these I used the [[Double Dispatch]] idiom several times. | ||
+ | * This design makes it reasonably easy for a plugin developer to shoot themselves in the foot, as it provides a lot of flexibility. Guidelines will need to be very clearly defined, both for myself and for anyone else who ever chooses to use the system. | ||
+ | |||
+ | The XML format also changed for this version. | ||
− | == | + | === Current codebase === |
− | + | [[Media:PluS_prototypeC.zip]] | |
− | + | == Future Work == | |
+ | More efficient ways of checking dependencies. Development of plugins (that'll be the real test). |
Latest revision as of 10:38, 5 October 2009
Contents |
The Problem
Many clubs or charitable organisations are staffed by volunteers, or staff with little training in computer use. Commonly used software (such as Micrososft Office, or accounting packages) are complex and can be difficult to use due to the vast array of options presented to the user. Most of the features will never be used by a particular person, yet all options are available regardless. This can leave users bewildered and unable to figure out which option they want.
Furthermore, some tasks require the use of multiple pieces of software. Integrating these creates another layer of complexity on the users part. For example, to create a contact directory for members, it can either be all typed up manually, or a database created and then combined with a template to create the directory. One option is more time consuming and error prone, the other more conceptually difficult for a novice user.
The Solution
I propose to create integrated, modular systems tailored to individual organisations. Customers would be able to choose from a variety of prewritten components, or choose to have custom components created, if there are not prewritten components to suit their needs. The first step in doing this is to build a plugin architecture which will support the loading and linking together of these components.
Requirements
The basic list
- Load required libraries for each component
- Create instance(s) of components
- Link components together
A little more detail
The basic function of the plugin system (henceforth known as PluS) is to load and activate plugins. Information about each plugin will be stored in an XML file, in a known location. Using this information, PluS will add the plugin's jar file to the classpath, create an instance of the plugin, and activate it.
PluS plugins can also declare dependencies and extensions. A dependency of a plugin is another plugin on which the first plugin relies. An extension of a plugin adds functionality to the first plugin. Any plugin is able to be depended upon. However, only plugins which define one or more extension points are able to be extended. In a dependency - dependent relationship, the dependent observes the dependency, while in an extension point - extension relationship, the extension point observes the extension, passing commands on to it as needed.
Just to throw a little extra into the pot, some basic logging functionality is always useful for debug purposes/spy work/remembering what you did or didn't do. It's a lot easier to switch one flag and change to a silent logger than to find and comment out every single printf statement in your code.
PluS in Action: An example
AddressBook is a contact directory, Boris is a VoIP application which defines an extension point for alternative dialing methods, and Calla is an extension for Boris which integrates with GTalk video chat. Boris doesn't have it's own contact list, but instead depends on AddressBook. As Boris first loads, it asks AddressBook for a list of contacts to display. When Calla is registered as an extension to Boris, an option is added to Boris' menu to call a contact using GTalk.
Mr. U. Ser decides to chat with his friend Wal using GTalk. U. Ser finds Wal in the contact list he can see in Boris. He chooses "GTalk chat" from the menu. Boris tells Calla to call Wal using the username from Wal's information stored in AddressBook. Calla calls Wal and notifies Boris when the call connects, so Boris can display the video feed. When the call disconnects, Calla notifies Boris again.
Supplement: The XML Plugin Description Format
First Design - Making it Work
How does this design work?
Core finds all the XML files in the plugins folder and throws them into a list. It then goes through each file in turn doing the following:
- A PluginDescription is created for each plugin
- The information is collected from the PluginDescription in order to create the Plugin
- The Plugin, along with a list of prerequisites and extension points, is registered with the PluginRegistry
Once all the plugins are registered, registrationComplete() is called, and the PluginRegistry activates each of the Plugins
The entry point for a Plugin must extend the abstract class Plugin
What's right with this design?
About the only thing I did vaguely right in this case was using the observer pattern a couple of times. The PluginRegistry is an Observer of each of the Plugins, and Plugins can also observe other Plugins. It made sense at the time.
I like having an enum for return codes rather than just returning an int or boolean.
I like having the XML parsing of the Plugin description distinct from the Plugin itself
What's wrong with this design?
Plenty. This was just a first prototype to see if the idea would even be doable. As such, I didn't put a lot of effort into designing anything at all.
Let's have a look at some Antipatterns, broken Design maxims and trampled wisdom:
- Poltergeist - The PluginDescription is a Poltergeist. Each Description appears and then disappears within a single run-through of a loop in the Core's main()
- Encapsulation - Core is doing a little more than it should, particularly in the way it handles the instantiation of Plugins. Rather than telling the PluginDescription to use the information contained in it to create a Plugin object, Core gets all the information from PluginDescription and creates the Plugin object itself. It then hands it off to the PluginRegistry. This breaks several of Riel's Heuristics: 2.1:Hide data within its class, 2.9:Keep related data and behavior in one place, 3.3:Beware of many accessors all relate to encapsulation. Core is also a potential God class
In other news, Core is never instantiated, and has only a main() and a static field plugins, which is the PluginRegistry.
Second Design - Making it Right? Maybe Not
What's improved with this design?
- Core (now PluginLoader) has lost some of its God Class tendencies. In this version it gets the list of files, creates XMLPluginReaders for each file, and tells the XMLPluginReader to register with the PluginRegistry.
- XMLPluginReader has shifted towards Tell, don't ask. It now gives the information needed to the PluginRegistry when it is asked to register instead of having multiple accessors.
What's still wrong with this design?
- There is some redundancy in PluginRegistry. There are two Maps indexed by PluginID. There should be a way of reducing that to one.
- XMLPluginReader is still a poltergeist, only existing within a loop within PluginLoader's run()
- Error handling is non-existent
- Extensions are not yet supported
Lightbulb Moments
The first
Extensions don't need to be plugins. Essentially they can just plug straight in to the extension point they are extending.
This was closely followed by the second
Extensions are plugins, just not plugins to the main system. Instead, they're plugins to the extension points.
This soon lead to the third
The main system is really just an extension point.
This sent me back to the drawing board for another redesign
Third Design - A shift in perspective
How does it work?
PluginLoader grabs the list of XML files from the plugins folder. It uses the ComponentBuilder to create a Component for each XML description. As each Component is create it is registered with the PluginLoader.
PluginLoader then checks that all the dependencies for each Component are available. If any Component is lacking a dependency it is removed from the set of Components. Currently the algorithm for doing this is very inefficient. Once all dependencies have been checked, the Components are told to instantiate their Plugin, and the PluginLinker connects all the Plugins together.
Each Component contains all the information related to its Plugin. This includes the ID of the host and extension point it connects to, as well as the Plugin's jar file, main class and dependencies.
PluS is the core system. It is essentially just a plugin in its own right, however it doesn't have a host. As they are connected together, the Plugins will naturally form a tree structure, with PluS as the root.
Plugins are responsible for managing their extension points. The registerExtension method takes in the ID of the extension point and the extending Plugin. It is up to the Plugin to connect the extension correctly.
Thoughts on the design
So far, this design has managed to stay quite small and unbloated. There aren't a lot of design patterns, which is not surprising given the small scope.
- Builder - ComponentBuilder is an example of the Builder design pattern. Creating a component involves parsing XML to extract all the relevant information and then using that information to create the Component. Abstracting this process out to a separate class keeps the PluginLoader much cleaner and tidier.
- Separation of concerns - I think this is better in the current design than the previous iteration. Keeping track of all the details for a particular Plugin has been abstracted out to Component. The information about the Plugin doesn't make sense to be kept inside the Plugin (why should a Plugin have to know what the name of the jar file it's packed in?), but does need to be kept nearby. Having the Component makes this simple. The linking together of Plugins has also been abstracted out to PluginLinker. Once again, this helps to keep PluginLoader cleaner as well as making sure PluginLoader isn't doing more than it should.
- Encapsulation - Using the Component class created a few issues with encapsulation. To get around some of these I used the Double Dispatch idiom several times.
- This design makes it reasonably easy for a plugin developer to shoot themselves in the foot, as it provides a lot of flexibility. Guidelines will need to be very clearly defined, both for myself and for anyone else who ever chooses to use the system.
The XML format also changed for this version.
Current codebase
Future Work
More efficient ways of checking dependencies. Development of plugins (that'll be the real test).