[Zope3-dev] EventService, references, and subscription semantics
Steve Alexander
steve@cat-box.net
Mon, 25 Feb 2002 15:24:04 +0000
This is an email to discuss various design decisions around the
EventService.
At present there is checked into Zope3 a file-system level EventService,
which is configured by zcml files.
An object can subscribe to this FS EventService, if it wants to receive
events.
The FS EventService keeps a list of subscribers, stored by python object
reference.
There is a zcml directive for subscribing a thing to the FS EventService.
Of course, if you subscribe something, and then restart z3.py, you have
to resubscribe it. So, subscriptions are not persisted.
This makes it difficult for a persistent object (in the ZODB) to be a
subscriber of the FS EventService, as it won't know when z3.py is
restarted, so it won't know when it needs to resubscribe to the FS
EventService.
So, only non-persistent things can subscribe to the FS EventService.
So, considering an FS EventService and a persistent ZODB EventService,
we now have two distinct semantics for the subscribe() method:
* a subscription that persists over server restarts
* a subscription that is lost at server restart
However, there is only one interface. This needs fixing.
Even if the FS EventService could store subscriptions between restarts
of z3.py (for example, in a DBM file), how would it store the references
to objects?
It could perhaps store them by location, and traverse the ZODB to get
the object. But then, we have different ways of subscribing an object
depending on whether it is a singleton object attached to a module (for
example, an FS Service), or it is a persistent object.
We also have three ways of refering to a subscribed object:
* a direct python object (or persistent object) reference
* a traversal path
* a fully qualified resolvable python foo.bar.baz thing
(We could also have a third: the ruid. However, the ObjectHub (which
manages ruids) depends on the EventService. So, the EventService can't
use ruids to do its job, otherwise we get an infinite regress of
ObjectHubs.)
Let's say we have a persistent ZODB EventService. Persistent objects uss
its subscribe() method to subscribe themselves to it.
Well, FS services can't subscribe to it using that same method, because,
even though the subscription is persisted between restarts, a singleton
FS Service is an object attached to a module, and will be a brand new
object each time z3.py restarts.
So, we'd need to have two distinct subscribe methods:
* subscribe() for persistent objects
* transientSubscribe() for non-persistent objects
And of course, some extra complexity in managing two collections of
subscriptions.
One solution would be to say that the FS EventService may only be
subscribed to by non-persistent objects, and the root ZODB EventService
may only be subscribed to by persistent objects.
However, we'd need a separate interface for the FS EventService, as its
subscribe() method would have different semantics than the persistent
EventService.
We could have something like this:
--------------------- ------------------
| IBaseEventService | | ISubscribable |
|===================| |================|
| publishEvent | | subscribe |
--------------------- | unsubscribe |
^ ------------------
| ^
| |
+-------------------+ |
| | |
------------------------ -------------------
| IFSEventService | | IEventService |
|======================| |=================|
| transientSubscribe | | |
| transientUnsubscribe | -------------------
------------------------
So, transient (ie non persisted) subscription is kept distinct from
subscription that persists between z3.py invocations.
FS things are only allowed to use the FSEventService.
Most code that wants to use the event service will get it by asking for
an IEventService. The root ZODB event service will propagate events to
the FSEventService.
Of course, it might turn out that we don't really need an
FSEventService. Although, I can see it being useful for using the
ComponentArchitecture outside of Zope3.
Then, we still have the issue of what to use for references.
We can use object references provided we're happy to only allow
persistent objects to be passed to subscribe().
We might want to have a range of subscribe methods:
* subscribePersistentObject
* subscribeTraversalLocation
* subscribeTransientObject (held onto by a weak reference perhaps)
* subscribeRuidObject (for subscribing an object via an object hub)
Anyway, there's a bunch of thoughts and conclusions.
I'd really like to refactor the EventService along the lines of the UML
diagram above, as I don't like having messy and conflicting semantics
for interfaces.
As for the rest; tell me what you think.
--
Steve Alexander