Personal tools
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

Interfaces

In Zope 3, everything is connected to an interface in some way. Sure enough, b-org has a slew of them. Getting the interface design right is often more than half the battle, so pay attention to this part.

Martin Aspeli

Plone 2.5 brings us closer to the promised land of Zope 3. Zope 3 brings us a new way of working. This tutorial will show how to marry the old and the new, to make Plone products that are more extensible, better tested and easier to maintain.
Page 7 of 15.

If you were trying to understand b-org without a comprehensive tutorial to hand, you would do well to look at the interfaces package. You will notice that this is subdivided into various files

interfaces/department.py
Contains a description of a department (IDepartment) and a marker interface for the content object that stores the department (IDepartmentContent).
interfaces/employee.py
Contains the equivalent interfaces, IEmployee and IEmployeeContent, as well as the definition of a specific event interface, IEmployeeModified.
interfaces/project.py
Again contains IProject and IProjectContent, as well ILocalWorkflowSelection, which is used to denote a utility that defines the placeful workflow policy that projects will use.
interfaces/workspace.py
Holds the interface IWorkspace, which is used by the local-role PAS plug-in to extract which users should have which local roles in a project.
interfaces/schema.py
Contains interfaces relevant to the custom schema extension mechanism - ISchemaExtender, IExtensibleSchemaProvider and ISchemaInvalidatedEvent.
interfaces/utils.py
Defines interfaces that are used as input to various vocabularies - IEmployeeLocator, IAddableTypesProvider and IValidRolesProvider.

In order to understand what each of these interfaces describes in more detail, look at the files above. Recall that interfaces are mainly documentation - these interfaces are accompanied by docstrings and generally self-documenting code.

The various interfaces intended for public consumption are imported to interfaces/__init__.py, so that client code can write, e.g.:

from Products.borg.interfaces import IEmployee
This is a common idiom. If you find yourself with too many interfaces to manage in interfaces/__init__.py, you don't necessarily need to do this, but it's probably a sign that you should be breaking your code into smaller packages!

Remember that unless you have a particular need to depend on Zope 2, then you don't need to pollute the Products namespace with such components! (and even if you do, with PythonProducts or Zope 2.10, you can do without the Products/ namespace too). For example, we could have placed the employee functionality in a package borg.employee, found in lib/python/borg/employee as a plain-python library, possibly depending on Zope 3 components (i.e. packages in the zope.* namespace).

Conversely, if you have relatively few interfaces, you can simply have an interfaces.py module without a directory.

Separating Archetypes from real components

One thing you may notice is that we have split the interface describing the concept of e.g. an employee (IEmployee) from the interface that describes the employee content object in the ZODB (IEmployeeContent). Whether this is always the right thing to do is debatable, but the reasoning goes something like this:

Archetypes objects contain a very large API. Archetypes schemas and the infamous ClassGen generate methods on the content objects corresponding to schema fields, so that a field name gets an accessor called getName() and a mutator called setName(). This is all rather Archetypes-specific, and in Zope 3 schemas, we typically prefer simple properties (a name attribute) to pairs of methods. To avoid being constrained by the Archetypes when defining interfaces (Archetypes is just one implementation choice), we created IEmployee as follows:
class IEmployee(Interface):
"""An employee, which is also a user.
"""

id = schema.TextLine(title=u'Identifier',
description=u'An identifier for the employee',
required=True,
readonly=True)

fullname = schema.TextLine(title=u'Full name',
description=u"The employee's full name for display purposes",
required=True,
readonly=True)
To support this, we could put the relevant properties into the Archetypes content object, but this is cumbersome, since the property() declaration normally used to convert methods to properties will only work when those methods actually exist, not when they are created by ClassGen.

Instead, we mark the content object with a marker interface, IEmployeeContent and then register an adapter to IEmployee. Strictly speaking, this is cheating, since the adapter makes assumptions about its context (such as which methods are available, and the fact that it uses Archetypes) that are not formally defined in the interface. To save excessive typing and retain some sanity in the interface definitions, it's not a terrible compromise though. Here's the adapter, from membership/employee.py:
class Employee(object):
"""Provide department information.
"""
implements(IEmployee)
adapts(IEmployeeContent)

def __init__(self, context):
self.context = context

@property
def id(self):
return self.context.getId()

@property
def fullname(self):
return self.context.Title()

Now, you can write:

emp = IEmployee(some_employee_content_object)
print emp.fullname

Another side-effect of this pattern is that we can separate things that are Archetypes-dependent from things that operate on the more general notion of an employee. For example, membrane generally makes assumptions about operating on Archetypes content objects, so the various membrane adapters adapt IEmployeeContent, whereas the view for charity employees is only concerned with "real" employees and so adapts the context to IEmployee.

This pattern is repeated for Departments and Projects as well.

Interfaces intended for utilities and adapters

Although interface design should generally not be too concerned with how those interfaces are implemented, you will often think "this is going to be used a a utility" or "this will most likely be an adapter". In this case, you may want to make some reference in the doc-string at least. For example, the ILocalWorkflowSelection interface states:
class ILocalWorkflowSelection(Interface):
"""A selection of a local workflow for projects.

This will normally be looked up as a utility.
"""

workflowPolicy = schema.TextLine(title=u'Workflow policy identifier',
description=u'The id of the placeful workflow policy to use',
required=True,
readonly=True)
Conversely, many interfaces are context-dependent, which means that most likely they will either be directly provided by a particular object or adaptable to it. Take the IAddableTypesProvider:
class IAddableTypesProvider(Interface):
"""A component capable of finding addable types in a given context.
"""

availableTypes = schema.Tuple(title=u'Available types',
description=u'A list of all addable types',
value_type=schema.Object(ITypeInformation))

defaultAddableTypes = schema.Tuple(title=u'Default addable types',
description=u'A list of types to be addable by default',
value_type=schema.Object(ITypeInformation))
The implication here is that client code will do something like:
from Products.borg.interfaces import IAddableTypesProvider
addableTypes = IAddableTypesProvider(context).availableTypes
Whether IAddableTypesProvider was provided directly by the context or (more likely) provided via an adapter is not important. The only time this distinction is really useful is in the case of marker interfaces, such as IEmployeeContent:
class IEmployeeContent(Interface):
"""Marker interface for employee content objects"""
These are often checked with providedBy():
assert IEmployeeContent.providedBy(employeeContentObject)
# we've got an employee, good
Again, the guiding principle here is separation of concerns. The aspect of a component that can provide a list of addable types (IAddableTypesProvider) is logically distinct from (and could be varied independently of) the aspect of a component that specifies it represents a project (IProject), even though it so happens that at present projects are the only time we concern ourselves with restricting addable types.

In the olden days, we would probably have put methods like getAvailableProjectAddableTypes() into the Project content type. Hopefully, you'll see why this is less optimal than having it in a separate component (hint: what if you in your customisation of b-org wanted to be much more particular about which types were addable?). You will hopefully start to pick up "fat" interfaces during interface design - if you had a neat IProject interface that described attributes of a project that were to be saved alongside the project object, and then found a couple of methods about defining addable types that were related to one another but not so much to the data of a project in general, you would hopefully reach for a new interface. If so - well done, you're getting there.
 
by Martin Aspeli last modified October 25, 2006 - 22:50 All content is copyright Plone Foundation and the individual contributors.

For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by