History for FrontPage
??changed:
-
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
<hr solid id=comments_below>
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.
[6 more lines...]