[Zope3-dev] Re: Services interfaces should not include mutators

Gary Poster gary@modernsongs.com
Sun, 22 Dec 2002 12:20:06 -0500


Steve Alexander wrote:
> 
>> This discussion provides yet more evidence for the rule of thumb that
>> service APIs should not contain mutators.
>>
>> In any case, I suggest a different factoring:
>>
>> - Define an event notification service that just supports event 
>> notification.
>>
>> - Define a global event subscription service.
> 
> 
> This can be defined by an interface, but it needn't be defined as a 
> serviceType. It would be used only via zcml, or from filesystem python 
> code.

On this basis of an earlier email from him about this, I think Jim's 
goal in the three-service division is that the publish code can then 
merely travel up the notification service, not having to switch over 
from local service name to global service name at the top: I understood 
this to be his main (and significant) dislike of the two-service 
division.  I am in favor of the three-service approach if only because 
it is a Pope-approved way of going forward.  If we want to simplify 
contextually-wrapped subscription for the alpha (as I think is 
important) then we have less than 24 hours before the Monday morning 
deadline.  So is that ok with you, Steve?

> See IGlobalAdapterService in 
> lib/python/Zope/ComponentArchitecture/GlobalAdapterService.py.
> 
> 
>> - Define a local event subscription service.
>>
>> Typically, components that provide the event notification service
>> will provide either the global event subscription service or the local
>> event subscription service.
> 
> 
> 
> Ok. So, how about the following three interfaces and two serviceTypes?
> 
> class IEventPublisher(Interface):
> 
>     def publish(event):
>         """Publish this event to subscribers.
> 
>         Events will often be propagated to higher level
>         IEventPublishers;
>         This is a policy decision for the IEventPublisher.
>         """
> 
> class IGlobalSubscribable(Interface):
> 
>     def globalSubscribe(subscriber, event_type=IEvent, filter=None):
>         """Add subscriber to the list of subscribers for the channel.
> 
>         subscriber must implement ISubscriber.
>         """
> 
> class ILocalSubscribable(Interface):
> 
>     def subscribe(path_or_object, event_type=IEvent, filter=None):
>         """Add subscriber to the list of subscribers for the channel.
> 
>         subscriber must implement ISubscriber.
>         """
> 
>     def subscribeHubId(hubid_or_object, event_type=IEvent, filter=None):
>         """Add subscriber to the list of subscribers for the channel.
> 
>         subscriber must implement ISubscriber.
>         """
> 
>     def listSubscriptions(path_or_object_or_hubid=None,
>                           event_type=None):
>         """List the subscriptions, filtered by subscriber and/or
>         event_type."""

Having been working on this for more than a few hours, I'm going with a 
different local subscribable interface (listed below).  This is with the 
understanding that the primary objective for the pre-alpha changes is 
to be able to subscribe securely and easily via wrapped object.  The 
rest of the interface approach that I'm implementing can be discussed ad 
nauseum later and ripped out post alpha, with my blessing: unless I get 
wildly negative responses, though, I claim the do-ers privelege of 
having a very limited time to implement and work already done.

> <serviceType id="Events" interface="IEventPublisher" />
> <serviceType id="Subscription" interface="ILocalSubscribable" />
> 
> 
> Vastly more components are interested in sending events than in 
> subscribing to receive them. Hence the short 'Events' and the longer 
> 'Subscription'.

That makes sense to me.  If you buy my explanation of Jim's three 
service approach, then I think 'GlobalSubscription' would be the third, 
trying to extend your logic.

OK, here's the interface I have been implementing with the old service 
division: I hope the changes I'll make on the basis of the new divisions 
are obvious.  I'll get on IRC so you can opt to flog me about it there, 
Steve. ;-)

class ILocalSubscribable(ISubscribable): # in the new plan, this will no
     # longer subclass ISubscribable and will not refer to it below.
     """A subscribable that only works with contextually wrapped objects
     or their paths or hubids."""

     def subscribe(subscriber, event_type=IEvent, filter=None):
         """Overrides the ISubscribable.subscribe method, limiting
         subscriber objects to those that are contextually wrapped, and
         expanding to allow paths or hubids that reference the
         subscriber.

         If the subscriber is a wrapped object then it will be
         subscribed on the basis of hubid, if available for the object,
         and path otherwise; passing the path or the hubid uses that
         explicitly.  In all cases, the method passes back the hubid or
         path used to subscribe on success.
         """

     def unsubscribe(subscriber, event_type=None, filter=None):
         """Overrides the ISubscribable.unsubscribe method, limiting
         subscriber objects to those that are contextually wrapped, and
         expanding to allow paths or hubids that reference the
         subscriber.

         If the subscriber is a wrapped object then it will be
         unsubscribed on the basis of both hubid, if available for the
         object, and path; passing the path or the hubid unsubscribes
         that only.

         unsubscribe must also accept paths and hubids that no longer
         resolve to an object, but if no subscriptions are found on the
         basis of the unicode string or integer, a NotFoundError is
         still raised.
         """

     def listSubscriptions(subscriber, event_type=None):
         """Overrides the ISubscribable.listSubscriptions method,
         limiting subscriber objects to those that are contextually
         wrapped, and expanding to allow paths or hubids that reference
         the subscriber.

         If the subscriber is a wrapped object then it will return an
         iterator of subscriptions on the basis of both hubid, if
         available for the object, and path; passing the path or the
         hubid lists subscriptions for that only.
         """

Gary