FrontPage
Problem
Many people reject object-oriented databases because of the difficulty of expressing complex relationships. ZODB is based on the object oriented data model which does not address this problem.
ZODB needs a reusable model for maintaining complex relationships between database objects. It also needs a basic implementation of that model.
The solution must be built on a well-known model for maintaining relationships. If we invent a new model that is hard to map onto any existing model, it will be difficult to know the right way to expand the model as requirements grow. The entity-relationship model is an established model to express relationships between entities. This model can also be used to express relationships between objects in the ZODB.
The solution must provide developers with ways to create relationships using only a few lines of Python code.
The solution must provide a very Pythonic way to access and maintain relationships.
Relationships should be bi-directional.
Not all objects are designed with relationships in mind. That should not prevent them from participating in relationships.
One should be able infer new relationships from existing ones.
Definitions
To be clear we define a relationship as a set of bidirectional references which can be discovered by objects on either side of the relationship. We do not use the term "relation" because it has a strong association with a database table for some.
Proposal
The implementation should be split up in two parts:
- Storing relationships
- Providing objects with views of relationships
Storing relationships
We store relationships in a central location because it enables us to infer new relationships without traversing the object system. This also makes it possible to store relationships between objects that are not designed with relationships in mind.
The developer does not need to create the relationship repository - it is globally available in a ZODB application.
An API will be provided for persisting relationships in ZODB. This API will create a relationship repository automatically if it does not already exist, enabling developers to mostly ignore the details of relationship storage.
Each relationship is represented by a single Relationship object that needs to be created by the developer eg.:
student_courses = Relationship()
API for Relationship
Max M's mxmRelations has a proven API for storing relationships
and forms the basis for this API. The API has been modified to
replace the word relation
with relationship
. Instances of
Relationship are stored in a global relationship repository.
class IRelationship(Interface): def relate(fromObjects, toObjects): """ Create relationships for each object in 'fromObjects' to each object in 'toObjects'. Both 'fromObjects' and 'toObjects' are sequences. """ def unrelate(fromObjects, toObjects): """ Removes relationships between objects. """ def delete(obj): """ Removes all relationships to the object. Used if an object is deleted. """ def get(obj): """ Returns all objects related to this object as a set, or an empty set. """
Example:
student_courses = Relationship() pete = Student() python101 = Course() zope101 = Course() student_courses.relate([pete], [python101, zope101]) assert student_courses.get(pete) == [python101, zope101] assert student_courses.get(python101) == [pete] student_courses.unrelate(pete, zope101)
Providing objects with views of relationships (RelationshipViews?)
We use descriptors (or in ZODB3, ComputedAttributes?) to provide an object-oriented view of relationships. The descriptor or computed attribute transparently accesses the relationship repository to provide relationships as attributes of the object. A relationship is defined at class level and is called a RelationshipView?. It should be made clear that a class is not required to define relationships if it wants to participate in relationships, this is only necessary to provide relationships as attributes of the object. ( The example above illustrates how objects that are ignorant of relationships can be related. )
Suggested implementation of a RelationshipView? descriptor:
class RelationshipView(object): def __init__(self, relationship, cardinality): self.relationship = relationship self.cardinality = cardinality def __get__(self, obj, cls=None): relatedObjects = self.relationship.get(obj) if self.cardinality == 'single': return relatedObjects[0] else: return relatedObjects def __set__(self, obj, value): if self.cardinality == 'single': value = [value] self.relationship.relate(obj, value)
Example:
student_courses = Relationship() course_teachers = Relationship() class Student: Courses = RelationshipView(student_courses, cardinality='multiple') class Course: Students = RelationshipView(student_courses, cardinality='multiple') Teacher = RelationshipView(course_teachers, cardinality='single') class Teacher: Courses = RelationshipView(course_teachers, cardinality='multiple') pete = Student() mary = Teacher() zope101 = Course() python101 = Course() pete.Courses.add(zope101) mary.Courses.add(python101) assert zope101.Teacher == mary assert python101.Students == [pete] zope101.Teacher = mary assert mary.Courses == [python101, zope101] assert pete.Courses[0].Teacher == mary
Risk factors
It might be a burden to support both ZODB 3 and ZODB 4.
Relationships and Metadata
Not enough has been said about metadata about relationships. We need to gather more use cases and extend the Relationship API with a metadata API
- evan (May 9, 2003 3:23 pm; Comment #1)
- I disagree with several aspects of this approach. Bi-directional relationships are inadequate for many applications, and even bidi is not enough for common cases without the ability to specify roles (i.e. am I the parent or the child?).
Cardinality should be specified in, and introspectable from, the relationship itself, not the related objects.
I suggest that Relationships be initialized with a specification of the relationship roles and their relative cardinality. One possible syntax involves using list nesting to indicate cardinality:
one-one: ['first', 'second'] one-many: ['parent', ['child']] many-many: [['thing1'], ['thing2']] one-one-many: ['mother', 'father', ['child']]