Service Oriented Architecture

Gaphor has a service oriented architecture. What does this mean? Well, Gaphor is build as a set of small islands (services). Each island provides a specific piece of functionality. For example: a service is responsible for loading/saving models, another for the menu structure, yet another service handles the undo system.

Service are defined as Egg entry points. With entry points applications can register functionality for specific purposes. Entry points are grouped in so called entry point groups. For example the console_scripts entry point group is used to start an application from the command line.

Entry points, as well as all major components in Gaphor, are defined around Zope interfaces. An interface defines specific methods and attributes that should be implemented by the class.

Entry point groups

Gaphor defines two entry point groups:

  • gaphor.services
  • gaphor.uicomponents

Services are used to add functionality to the application. Plug-ins should also be defined as services. E.g.:

entry_points = {
    'gaphor.services': [
        'xmi_export = gaphor.plugins.xmiexport:XMIExport',
    ],
},

There is a thin line between a service and a plug-in. A service typically performs some basic need for the applications (such as the element factory or the undo mechanism). A plug-in is more of an add-on. For example a plug-in can depend on external libraries or provide a cross-over function between two applications.

Interfaces

Each service (and plug-in) should implement the gaphor.interfaces.IService interface:

UI components is another piece of cake. The gaphor.uicomponents entry point group is used to define windows and user interface functionality. A UI component should implement the gaphor.ui.interfaces.IUIComponent interface:

Typically a service and UI component would like to present some actions to the user, by means of menu entries. Every service and UI component can advertise actions by implementing the gaphor.interfaces.IActionProvider interface:

Example plugin

A small example is provided by means of the Hello world plugin. Take a look at the files at Github.

The setup.py file contains an entry point:

entry_points = {
    'gaphor.services': [
        'helloworld = gaphor.plugins.helloworld:HelloWorldPlugin',
    ]
}

This refers to the class HelloWorldPlugin in package/module gaphor.plugins.helloworld.

Also notice that, since the package is defined as gaphor.plugins, which is also a package in the default gaphor distribution, each package level contains a special statement in its __init__.py that tells setuptools its namespace declaration::

__import__('pkg_resources').declare_namespace(__name__)

Here is a stripped version of the hello world class:

from zope import interface
from gaphor.interfaces import IService, IActionProvider
from gaphor.core import _, inject, action, build_action_group


class HelloWorldPlugin(object):

    interface.implements(IService, IActionProvider)  # 1.

    gui_manager = inject('gui_manager')              # 2.

    menu_xml = """                                   # 3.
      <ui>
        <menubar name="mainwindow">
          <menu action="help">
            <menuitem action="helloworld" />
          </menu>
        </menubar>
      </ui>
    """

    def __init__(self):
        self.action_group = build_action_group(self) # 4.

    def init(self, app):                             # 5.
        self._app = app

    def shutdown(self):                              # 6.
        pass

    @action(name='helloworld',                       # 7.
            label=_('Hello world'),
            tooltip=_('Every application ...'))
    def helloworld_action(self):
        main_window = self.gui_manager.main_window   # 8.
        pass # gtk code left out
  1. As stated before, a plugin should implement the IService interface. It also implements IActionProvider, saying it has some actions to be performed by the user.
  2. The plugin depends on the gui_manager service. The gui_manager can be referenced as a normal attribute. The first time it’s accessed the dependency to the gui_manager is resolved.
  3. As part of the IActionProvider interface an attribute menu_xml should be defined that contains some menu xml (see http://developer.gnome.org/doc/API/2.0/gtk/GtkUIManager.html#XML-UI).
  4. IActionProvider also requires an action_group attribute (containing a gtk.ActionGroup). This action group is created on instantiation. The actions itself are defined with an action decorator (see 7).
  5. Each IService has an init(app) method...
  6. ... and a shutdown() method. Those can be used to create and detach event handlers for example.
  7. The action that can be invoked. The action is defined and will be picked up by build_action_group() (see 4.)
  8. The main window is retrieved from the gui_manager. At this point the “inject’ed” gui-manager reference is resolved.