Service Oriented Architecture#
Gaphor has a service oriented architecture. What does this mean? Well, Gaphor is built as a set of small islands (services). Each island provides a specific piece of functionality. For example, we use separate services to load/save models, provide the menu structure, and to handle the undo system.
We define services as entry points in the pyproject.toml
. With entry points,
applications can register functionality for specific purposes. We also group
entry points in to entry point groups. For example, we use the
console_scripts entry point group to start an application from the command
line.
Services#
Gaphor is modeled around the concept of services. Each service can be registered with the application and then it can be used by other services or other objects living within the application.
Each service should implement the Service interface. This interface defines one method:
shutdown(self)
Which is called when a service needs to be cleaned up.
We allow each service to define its own methods, as long as the service is implemented too.
Services should be defined as entry points in the pyproject.toml
file.
Typically, a service does some work in the background. Services can also expose actions that can be invoked by users. For example, the Ctrl-z key combo (undo) is implemented by the UndoManager service.
A service can also depend on another services. Service initialization resolves these dependencies. To define a service dependency, just add it to the constructor by its name defined in the entry point:
class MyService(Service):
def __init__(self, event_manager, element_factory):
self.event_manager = event_manager
self.element_factory = element_factory
event_manager.subscribe(self._element_changed)
def shutdown(self):
self.event_manager.unsubscribe(self._element_changed)
@event_handler(ElementChanged)
def _element_changed(self, event):
Services that expose actions should also inherit from the ActionProvider
interface. This interface does not require any additional methods to be
implemented. Action methods should be annotated with an @action
annotation.
Example: ElementFactory#
A nice example of a service in use is the ElementFactory. It is one of the core services.
The UndoManager depends on the events emitted by the ElementFactory. When an
important events occurs, like an element is created or destroyed, that event is
emitted. We then use an event handler for ElementFactory that stores the
add/remove signals in the undo system. Another example of events that are
emitted are with UML.Element
s. Those classes, or more specifically, the
properties, send notifications every time their state changes.
Entry Points#
Gaphor uses a main entry point group called gaphor.services
.
Services are used to perform the core functionality of the application while breaking the functions in to individual components. For example, the element factory and undo manager are both services.
Plugins can also be created to extend Gaphor beyond the core functionality as
an add-on. For example, a plugin could be created to connect model data to
other applications. Plugins are also defined as services. For example a new XMI
export plugin would be defined as follows in the pyproject.toml
:
[tool.poetry.plugins."gaphor.services"]
"xmi_export" = "gaphor.plugins.xmiexport:XMIExport"
Interfaces#
Each service (and plugin) should implement the gaphor.abc.Service
interface:
Another more specialized service that also inherits from gaphor.abc.Service
,
is the UI Component service. Services that use this interface are used to
define windows and user interface functionality. A UI component should
implement the gaphor.ui.abc.UIComponent
interface:
- class gaphor.ui.abc.UIComponent[source]#
A user interface component.
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.abc.ActionProvider
interface:
Example plugin#
A small example is provided by means of the Hello world plugin. Take a look at the files at GitHub. The example plugin needs to be updated to support versions 1.0.0 and later of Gaphor.
The pyproject.toml file contains a plugin:
[tool.poetry.plugins."gaphor.services"]
"helloworld" = "gaphor_helloworld_plugin:HelloWorldPlugin"
This refers to the class HelloWorldPlugin
in package/module
gaphor_plugins_helloworld.
Here is a stripped version of the hello world plugin:
from gaphor.abc import Service, ActionProvider
from gaphor.core import _, action
class HelloWorldPlugin(Service, ActionProvider): # 1.
def __init__(self, tools_menu): # 2.
self.tools_menu = tools_menu
tools_menu.add_actions(self) # 3.
def shutdown(self): # 4.
self.tools_menu.remove_actions(self)
@action( # 5.
name="helloworld",
label=_("Hello world"),
tooltip=_("Every application…"),
)
def helloworld_action(self):
main_window = self.main_window
pass # gtk code left out
As stated before, a plugin should implement the
Service
interface. It also implementsActionProvider
, saying it has some actions to be performed by the user.The menu entry will be part of the “Tools” extension menu. This extension point is created as a service. Other services can also be passed as dependencies. Services can get references to other services by defining them as arguments of the constructor.
All action defined in this service are registered.
Each service has a
shutdown()
method. This allows the service to perform some cleanup when it’s shut down.The action that can be invoked. The action is defined and will be picked up by
add_actions()
method (see 3.)