Eventing in Services

Revision as of 16:49, 16 December 2008 by Paulb (Talk | contribs) (Creating an Observer)


Construction.png Not Ready for Review: This Page is Under Development!

Event specification is a little different from using action sequences in EiffelVision2 or in other action-based eventing systems. ESS should not use ACTION_SEQUENCE like in these other systems but a specialized type called EVENT_TYPE. There is a support framework for ESS wrapped around EVENT_TYPE and a observer design pattern to simplify use.

Event vs Action

A new eventing model was used in ESS as not to conflict of alter the existing mechanisms in EiffelBase. In addition, services act on or raises an "event". With respects to EiffelVision2 an action is event that is performed in response to some action.

Interface Designs

When designing interfaces or a service interface the event type to use is the base event type interface EVENT_TYPE_I. EVENT_TYPE is actually the default implementation class for EVENT_TYPE_I. The difference between EVENT_TYPE_I and its implementation EVENT_TYPE is EVENT_TYPE also implements EVENT_TYPE_PUBLISHER_I. With respects to correct encapsulation design, an interfaces should always use EVENT_TYPE_I because no interface should allow a client to publish an event. Events should only be raised in context.

Interface Event Declarations

Take the following rudimentary interface definition with a single event changed_event:

deferred class
    INTERFACE_I
 
inherit
    USABLE_I
 
feature -- Events
 
    changed_event: !EVENT_TYPE_I [TUPLE [flags: INTEGER]]
            -- Events call when a change occurs
        require
            is_interface_usable: is_interface_usable
        deferred
        ensure
            result_consistent: Result ~ changed_event
            result_is_interface_usable: Result.is_interface_usable
        end
 
end

The first point to recognize is the name of the event feature is singular and not plural. It is, after all, an event and not a collection of events. An event will have multiple actions subscribed to it, all called when published, but it is still a single event.

The design opts to define a deferred feature for specification of the event. As with all interface definitions should be free from all specific implementation and bindings to a particular declaration style, unless necessary of course. The implementation is now free to implement the event as a once, once-per-object, self-attribute or using a lazy-instantiation model (the latter being commonly used for efficient memory management and clean-up.)

Finally, the Result type of the event feature is EVENT_TYPE_I and not the more specific EVENT_TYPE. As previously mentioned, the latter effective type contains means to publish the events, which should not be accessed by any client.

Supporting Events

With this diminutive example there seems no need for any support for a single event, when using multiple events it becomes more necessary to support the interface. Here support comes in the form of providing an observer to all the events on a given interface. Utilizing an observer is a quick way for multiple clients to subscribe to an interface events. As an added feature, the implementation using observers is less error prone to memory leaks associated with failure to unscribed to all scribed-to events.

Creating an Observer

There are a number of guidelines when designing an observer for an interface's declared events, and just one requirement:

Recommended.png Recommended: Observer names should contain the name of the interface it observers and suffixed with _OBSERVER. For the INTERFACE_I, drop the _I (or _S for services) interface declaration suffix and append the _OBSERVER suffix yielding INTERFACE_OBSERVER.

The second design guideline is in regards to naming the event handler routines, those called when an associated event is published. Like the class name, the event handling routine name should reflect an association with the event it's handling. Removing the _event suffix from the event feature and prefixing it with on_ conveys the association with the event. For the changed_event the observer's event handler name would yield on_changed.

The requirement is the observer actually be a effective class, more on this later. As the class has to be effective so do all event handler routines. Regardless of the deferred/effective status requirement of the observer class in the future, the event handler routines should always be effective. This removes the need to implement empty handler routines for a class implementing the observer. With a complete collection of effective handlers, the implementing class can redefine only what is needs to be notified of. Finally, the base event handler routines in the observer should not contain any implementation.

class
    INTERFACE_OBSERVER
 
feature -- Event Handler
 
    on_changed (a_flags: INTEGER)
            --
        require
            is_interface_usable: {l_usable: USABLE_I} Current implies l_usable.is_interface_usable
        do
        end
 
end

The second step to supporting an interface's events is to provide access to a event connection point. A connection is the facade necessary to establish a connection with a set of interface events with a set of event handlers. There is a little trickery involved here and is a necessary evil in order to create a clean and versitle model for observers.

Again, there is a recommended rule to naming here. It is not recommended to simply call a connection access attribute/function connection. Multiple interfaces maybe implemented by the same class and as a result cause conflicts which have to be renamed. In such as case all connection point routines should be renamed for the sake of API consistency.

Recommended.png Recommended: To pre-emptively avoid the clashing and to create APIs that are simplier to utilize and more consistent, name the connection point routine using the name of the interface suffix with _connection. Again, like the observer's class name, remove the _I (or _S) suffix from the class name. Of course, it goes without saying that the name should be in lower case.

deferred class
    INTERFACE_I
 
...
 
feature -- Access
 
    interface_connection: EVENT_CONNECTION_I [INTERFACE_OBSERVER, INTERFACE_I]
            -- Connection point for {INTERFACE_I} events.
        require
            is_interface_usable: is_interface_usable
        local
            l_target: INTERFACE_OBSERVER
        attribute
                -- Create the event-action bindings
            create l_target
            create {EVENT_CONNECTION [INTERFACE_OBSERVER, INTERFACE_I]}
                Result.make_from_array (<<[changed_event, agent l_target.on_changed]>>)
        end
 
feature -- Events
 
...
 
end

Event Connections and Observers

To Su