[ZODB-Dev] Implementing Storage Decorators
Jim Fulton
jim at zope.com
Wed May 2 11:52:41 EDT 2007
I really like the model of providing storage functionality by
combining storage decorators. A decorator is a component that
augments and possibly changes the interfaces provided by a storage.
For example, the BlobStorage adds support for BLOBS to existing
storages, using a wrapped storage to hold object data. Version 2 of
Zope Replication Services (ZRS2) is implemented with storage
decorators. It would be useful to have decorators that provides
services like statitics reporting, data compression, and data
encryption.
A decorator will wrap some storage and delegate some calls to it. In
some cases, it will modify a method, providing it's own logic and
possibly calling the method of the wrapped storage. In other cases,
it will use methods of the underlying storage without change. I
suspect that using underlying storage methods is most common.
A question arises as to how to implement storage decorators.
BlobStorage uses zope.proxy. This fully automates delegating to
methods of underlying storages without change and makes overriding
methods easy enough. This is the approach that BlobStorage uses.
Another aproach is to copy storage methods into the decorator's
instance dictionary during initialization of the decorator. This is
the approach taked by databases, connections, and storage servers.
This is also the approach taken by ZRS2.
When BlobStorage was implemented, the ZODB storage interfaces were
almost undefined. It was very difficult to know which methods were
available and which needed to be proxied. Using zope.proxy was the
sanest approach.
Now that I think we have well-defined storage interfaces, the method
copying approach is more appealing to me for two reasons:
- It is explicit. The author of the decorator has to deal with every
method, even if only by naming the methods they want to copy.
I like being explicit, especially for something as complex as the
storage interface. BaseStorage is an example of why I don't like
implicit. It makes it easy to implicitly implement methods incorrectly.
- If we used it everywhere, we could avoid a dependency on zope.proxy.
Some downsides of copying:
- It's a pain to support optional interfaces. The copying code has
to do some duck typing to decide what to copy. This might be cleaned
up if interface declarations were used. We haven't bothered to make
interface declarations for storages to date, because we haven't had
decent interfaces to declare.
- It's a pain to support old ZODB versions. Hopefully, going
forward, this won't matter, but who knows.
- If we want to start using interface declarations, the decorators
will have to copy interface declations from the wrapped storage. I
think that this can be as simple as::
zope.interface.directlyProvides(
self, zope.interface.providedBy(self._storage))
There's a base class that automates decorator interface-
declaration management when using zope.proxy.
I think I still rather like explicit, but I'm on the fence about
which approach is best. What do other people think?
Jim
--
Jim Fulton mailto:jim at zope.com Python Powered!
CTO (540) 361-1714 http://www.python.org
Zope Corporation http://www.zope.com http://www.zope.org
More information about the ZODB-Dev
mailing list