Michal's Design Study

From CSSEMediaWiki
Revision as of 12:27, 3 October 2009 by Michal Connole (Talk | contribs)
Jump to: navigation, search

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

PluS XML format

First Design - Making it Work

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:

  1. A PluginDescription is created for each plugin
  2. The information is collected from the PluginDescription in order to create the Plugin
  3. 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

Mkc design2.png

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

Stuff I currently need help with

Plugins shouldn't be loaded if not all their dependencies are there. However, their dependencies may exist but not be loaded until later... Any ideas on how to deal with this? Information on each plugin & its dependencies etc is given in a separate xml file.

I need a better name than Boris for my example. Preferably something starting with B that is related to VoIP.

Personal tools