Edgewall Software

Changes between Version 30 and Version 31 of TracDev/ComponentArchitecture


Ignore:
Timestamp:
Feb 8, 2015, 12:15:13 PM (9 years ago)
Author:
figaro
Comment:

Cosmetic changes

Legend:

Unmodified
Added
Removed
Modified
  • TracDev/ComponentArchitecture

    v30 v31  
    1 = Trac Component Architecture =
    2 
    3 As the heart of Trac, [source:/trunk/trac/core.py#latest trac.core] implements a minimal ''component kernel'' that allows components to easily extend each others' functionality. It provides a ''"meta-plugin-API"'': every component can easily offer its own plugin API by declaring ''"extension points"''.
    4 
    5 == What is a Component? ==
    6 
    7 For our purposes, a ''component'' is an object that provides a certain type of service within the context of the application. There is at most one instance of any component: components are singletons. That implies that a component does '''not''' map to an entity of the application's object model; instead, components represent functional subsystems.
    8 
    9 Components can declare ''"extension points"'' that other components can “plug in” to. This allows one component to enhance the functionality of the component it extends, without the extended component even knowing that the extending component exists. All that is needed is that the original component exposes – and uses – one or more extension points.
     1= Trac Component Architecture
     2
     3As the heart of Trac, [source:/trunk/trac/core.py#latest trac.core] implements a minimal '''component kernel''' that allows components to easily extend each others' functionality. It provides a meta-plugin-API: every component can easily offer its own plugin API by declaring '''extension points'''.
     4
     5== What is a Component?
     6
     7A '''component''' is an object that provides a certain type of service within the context of the application. There is at most one instance of any component: components are singletons. That implies that a component does '''not''' map to an entity of the application's object model; instead, components represent functional subsystems.
     8
     9Components can declare '''extension points''' that other components can plug in to. This allows one component to enhance the functionality of the component it extends, without the extended component even knowing that the extending component exists. All that is needed is that the original component exposes – and uses – one or more extension points.
    1010
    1111[[Image(xtnpt.png)]]
    1212
    13 A component can extend any number of other components and still offer its own extension points. This allows a plugin to itself offer a plugin API (i.e. extension point). This feature is the basis for a plugin-based architecture.
    14 
    15 The actual functionality and APIs are defined by individual components. The component kernel provides the “magic glue” to hook the different subsystems together – without them necessarily knowing about each other.
    16 
    17 == Public classes ==
     13A component can extend any number of other components and still offer its own extension points. This allows a plugin to itself offer a plugin API, ie extension point. This feature is the basis for a plugin-based architecture.
     14
     15The actual functionality and APIs are defined by individual components. The component kernel provides the glue to hook the different subsystems together, without them necessarily knowing about each other.
     16
     17== Public classes
    1818
    1919`trac.core.ComponentManager` [[BR]]
    20   Manages component life cycle, instantiating registered components on demand
     20  Manages component life cycle, instantiating registered components on demand.
    2121
    2222`trac.core.Component` [[BR]]
     
    3131[[Image(comparch.png)]]
    3232
    33 == Declaring a component ==
     33== Declaring a component
    3434
    3535The simplest possible component is an empty class derived from `trac.core.Component`:
     
    5151}}}
    5252
    53 Remember that components follow the singleton pattern, but component managers do not. There is only one active instance of any component per component manager. The component constructor is “magic” in that it checks with the component manager whether there´s already an active instance before allocating a new instance. If the component was already instantiated, the existing instance is returned:
     53Remember that components follow the singleton pattern, but component managers do not. There is only one active instance of any component per component manager. The component constructor checks with the component manager whether there is already an active instance before allocating a new instance. If the component was already instantiated, the existing instance is returned:
    5454
    5555{{{
     
    6060}}}
    6161
    62 If a component needs to initialize data members, it can override the `__init__` method. But because the component manager needs to be able to instantiate the component on demand, `__init__` must ''not'' require any extra parameters, ''including'' the reference to the component manager passed into the constructor:
     62If a component needs to initialize data members, it can override the `__init__` method. But because the component manager needs to be able to instantiate the component on demand, `__init__` must '''not''' require any extra parameters, including the reference to the component manager passed into the constructor:
    6363
    6464{{{
     
    7474}}}
    7575
    76 Direct `Component` sub-classes also do not need to worry about invoking the base class' (i.e. `Component`'s) `__init__` method, as it's empty.
    77 
    78 Notes:
    79  * You can't pass data to the constructor of a component.
    80 
    81 === Components instantiating other Components ===
     76Direct `Component` sub-classes also do not need to worry about invoking the base class' (i.e. `Component`'s) `__init__` method, as it is empty.
     77
     78'''Note''': You can't pass data to the constructor of a component.
     79
     80=== Components instantiating other Components
    8281
    8382If one `Component` instantiates another, it typically will use the same `ComponentManager`, instead of creating a new `ComponentManager`.
     
    9089}}}
    9190
    92 Note that within trac, the component manager is more commonly referenced as `self.env`.
    93 
    94 == Declaring an extension point ==
     91Note that within Trac, the component manager is more commonly referenced as `self.env`.
     92
     93== Declaring an extension point
    9594
    9695The component above doesn't actually do anything. Making an object a component only makes it act as a singleton in the scope of a component manager, which isn't that exciting in itself.
    9796
    98 The real value of components becomes clearer when the facilities for extensions are used. As a simple example, the following component provides an extension point that lets other components listen to changes to the data it manages (in this case a list of to-do items) – following the widely known observable pattern:
     97The real value of components becomes clearer when the facilities for extensions are used. As a simple example, the following component provides an extension point that lets other components listen to changes to the data it manages (in this case a list of to-do items), following the widely known observable pattern:
    9998
    10099{{{
     
    121120Here, the `TodoList` class declares an extension point called `observers` with the interface `ITodoObserver`. The interface defines the ''contract'' that extending components need to conform to.
    122121
    123 The `TodoList` component notifies the observers inside the `add()` method by iterating over `self.observers` and calling the `todo_added()` method for each. This works because the `observers` attribute is a [http://users.rcn.com/python/download/Descriptor.htm descriptor]: When it is accessed, it finds all ''enabled'' components (see [#component_lifecycle below]) that declare to extend the extension point. For each of those components, it gets the instance from the component manager, potentially activating it if it is getting accessed for the first time.
    124 
    125 Note that there are actually three ways to define an extension point:
     122The `TodoList` component notifies the observers inside the `add()` method by iterating over `self.observers` and calling the `todo_added()` method for each. This works because the `observers` attribute is a [http://users.rcn.com/python/download/Descriptor.htm descriptor]: When it is accessed, it finds all '''enabled''' components (see [#component_lifecycle below]) that declare to extend the extension point. For each of those components, it gets the instance from the component manager, potentially activating it if it is getting accessed for the first time.
     123
     124Note that there are multiple ways to define an extension point:
    126125
    127126 * `trac.core.ExtensionPoint`: This is an ''unordered list of all'' enabled components implementing a specific extension point interface.
    128127 * `trac.config.ExtensionOption`: An option for [wiki:TracIni trac.ini] that describes ''exactly one'' enabled component implementing a specific extension point interface.
    129  * `trac.config.OrderedExtensionsOption`: An option for [wiki:TracIni trac.ini] that describes an ''ordered list'' of enabled components implementing a specific extension point interface. (Components that also implement the same interface but are not listed in the option can automatically be appended to the list.)
    130 
    131 == Plugging in to an extension point ==
     128 * `trac.config.OrderedExtensionsOption`: An option for [wiki:TracIni trac.ini] that describes an ''ordered list'' of enabled components implementing a specific extension point interface. Components that also implement the same interface but are not listed in the option can automatically be appended to the list.
     129
     130== Plugging in to an extension point
    132131
    133132Now that we have an extendable component, let's add another component that extends it:
     
    149148You can specify multiple extension point interfaces to extend with the `implements` method by simply passing them as additional arguments.
    150149
    151 == Putting it together ==
     150== Putting it together
    152151
    153152Now that we've declared both a component exposing an extension point, and another component extending that extension point, let's use the to-do list example to see what happens:
     
    173172}}}
    174173
    175 This output obviously comes from the `TodoPrinter`. Note however that the code snippet above doesn't even mention that class. All that is needed to have it participating in the action is to declare the class. ''(That implies that an extending class needs to be imported by a python script to be registered. The aspect of loading components is however separate from the extension mechanism itself.)''
     174This output obviously comes from the `TodoPrinter`. Note however that the code snippet above doesn't even mention that class. All that is needed to have it participating in the action is to declare the class. That implies that an extending class needs to be imported by a python script to be registered. The aspect of loading components is however separate from the extension mechanism itself.
    176175
    177176== Component Lifecycle and State == #component_lifecycle
     177
    178178This section will shed some light on how components come to be. It describes some inner workings and terminology that you may come across when trying to understand components, and provides information about where the specific parts are implemented.
    179179
     
    191191  * by listing the file in `entry_points` section of a plugin (see [wiki:TracDev/PluginDevelopment#Packagingplugins Packaging Plugins])
    192192
    193  The registration is handled by `trac.core.ComponentMeta` using [http://www.ibm.com/developerworks/linux/library/l-pymeta.html metaclass programming].
    194  2. ''Activation:'' A component gets activated when it's first used. So, "activation" is basically just another word for "instantiation", though since a component is a singleton it's only activate/instantiated once. When a component gets activated, Trac's component manager adds some useful fields to the component (specifically: `env`, `log`, and `config`).[[BR]]
     193 The registration is handled by `trac.core.ComponentMeta` using [http://www.ibm.com/developerworks/linux/library/l-pymeta3/index.html metaclass programming].
     194 2. ''Activation:'' A component gets activated when it's first used. So, "activation" is basically another word for "instantiation", though since a component is a singleton it's only activated/instantiated once. When a component gets activated, Trac's component manager adds some useful fields to the component, specifically: `env`, `log`, and `config`.[[BR]]
    195195    A component can be activated using one of the following methods:
    196       * when it's manually constructed for the first time (using `Component(compmngr)`)
     196      * when it's manually constructed for the first time, using `Component(compmngr)`
    197197      * when one of the extension points the component implements is used for the first time. Note that the component must be enabled for this way to work (see below).
    198198   
     
    207207Enabling a component is done in the `[components]` section of [wiki:TracIni trac.ini]. This is implemented in `trac.env.Environment.is_component_enabled()`. Whether a component is enabled or disabled is checked ''only once'' when an extension point that component implements is first used.
    208208
    209 ''Miscellaneous notes:''
     209'''Miscellaneous notes:'''
    210210* Components can be marked "abstract". This is done simply by adding a member field `abstract = True` to the component class.
    211211  {{{
     
    219219* Components should be listed in the `entry_points` section, if they define any options (from `trac.config`). This way `trac.ini` editors can find this option even if it still has its default value. Options are registered when the component is registered. The component that defines the option doesn't need to be enabled for the option to be registered and can even be abstract.
    220220
    221 
    222 == How components are used in Trac's code ==
    223 
    224 The typical use of components in Trac starts with a top-level “service provider” that we'll pick up and use directly. Take, for example, `trac.perm.PermissionSystem`:
     221== How components are used in Trac's code
     222
     223The typical use of components in Trac starts with a top-level '''service provider''' that we'll pick up and use directly. Take, for example, `trac.perm.PermissionSystem`:
    225224{{{
    226225#!python
     
    270269
    271270}}}
    272 The above adds an option called `permission_store` to `trac.ini`, declares that the component named by the option implements `IPermissionStore`, and sets its default to `DefaultPermissionStore`.  See [browser:trunk/trac/config.py trac.config] for `ExtensionOption` and friends.  Methods of service providers such as `PermissionSystem` are commonly a thin forwarding layer over such an `ExtensionOption`.  For example:
     271
     272The above adds an option called `permission_store` to `trac.ini`, declares that the component named by the option implements `IPermissionStore`, and sets its default to `DefaultPermissionStore`. See [browser:trunk/trac/config.py trac.config] for `ExtensionOption` and friends. Methods of service providers such as `PermissionSystem` are commonly a thin forwarding layer over such an `ExtensionOption`. For example:
     273
    273274{{{
    274275#!python
     
    280281        return self.store.get_all_permissions()
    281282}}}
     283
    282284Thus, service providers are directly manipulated from Python, and are customized through the automatic aggregation of components implementing `ExtensionPoint`s and through configuration of `ExtensionOption`s by Trac administrators.
    283285