Difference between revisions of "Eventing in Services"
(→Interface Designs) |
(→Supporting Events) |
||
(9 intermediate revisions by the same user not shown) | |||
Line 16: | Line 16: | ||
<e> | <e> | ||
deferred class | deferred class | ||
− | + | MY_INTERFACE_I | |
inherit | inherit | ||
− | + | INTERFACE_I | |
feature -- Events | feature -- Events | ||
− | changed_event: !EVENT_TYPE_I [TUPLE [flags: INTEGER]] | + | changed_event: !EVENT_TYPE_I [TUPLE [sender: MY_INTERFACE_I; flags: INTEGER]] |
-- Events call when a change occurs | -- Events call when a change occurs | ||
require | require | ||
Line 35: | Line 35: | ||
end | end | ||
</e> | </e> | ||
− | 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 | + | 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 event handlers 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 | + | 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, using a lazy-instantiation model (the latter being commonly used for efficient memory management and clean-up) or a façade to another interface. |
− | Finally, the <e>Result</e> type of the event feature is <e>EVENT_TYPE_I</e> and not the more specific <e>EVENT_TYPE</e>. As previously mentioned, the latter effective type contains means to publish the events, which should not be accessed by any client. | + | Finally, the <e>Result</e> type of the event feature is <e>EVENT_TYPE_I</e> and not the more specific implementation type <e>EVENT_TYPE</e>. As previously mentioned, the latter effective type contains means to publish the events, which should not be accessed by any client of the interface. |
=== Supporting Events === | === Supporting Events === | ||
+ | With such a diminutive example there seems no need for any support for a single event, when using multiple events it becomes more evident and necessary to support the interface. Eventing support comes in the form of providing an implementation observer pattern, which can autonomously subscribe 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 un-subscribed to all subscribed 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|Observer names should contain the name of the interface it observers and suffixed with <e>_OBSERVER</e>. For the <e>INTERFACE_I</e>, drop the <e>_I</e> (or <e>_S</e> for services) interface declaration suffix and append the <e>_OBSERVER</e> suffix yielding <e>INTERFACE_OBSERVER</e>. | ||
+ | <br/><br/> | ||
+ | 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 <e>_event</e> suffix from the event feature and prefixing it with <e>on_</e> conveys the association with the event. For the <e>changed_event</e> the observer's event handler name would yield <e>on_changed</e>. | ||
+ | <br/><br/> | ||
+ | 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.}} | ||
+ | |||
<e> | <e> | ||
class | class | ||
− | + | MY_INTERFACE_OBSERVER | |
+ | |||
+ | inherit | ||
+ | EVENT_OBSERVER_I | ||
feature -- Event Handler | feature -- Event Handler | ||
− | on_changed (a_flags: INTEGER) | + | on_changed (a_sender: MY_INTERFACE_I; a_flags: INTEGER) |
-- | -- | ||
require | require | ||
is_interface_usable: {l_usable: USABLE_I} Current implies l_usable.is_interface_usable | is_interface_usable: {l_usable: USABLE_I} Current implies l_usable.is_interface_usable | ||
+ | a_sender_is_interface_usable: a_sender.is_interface_usable | ||
+ | is_valid_flags_a_flags: a_sender.is_valid_flags (a_flags) | ||
do | do | ||
end | end | ||
end | end | ||
+ | |||
+ | The observer inherits <e>EVENT_OBSERVER_I</e>, an interface class currently containing no declarations. It's used currently for identity and potential future additions required to support observing events. <e>EVENT_OBSERVER_I</e> does not use the [[Usable Pattern|usable pattern]] and so access to the queiries of <e>USABLE_I</e> are unavailable. However, in ESS it is very likely a descenent of the observer will implement <e>USABLE_I</e> and should be supported regardless of <e>EVENT_OBSERVER_I</e>'s hierarchy. Be sure to add the precondition | ||
+ | |||
+ | <e> | ||
+ | is_interface_usable: {l_usable: USABLE_I} Current implies l_usable.is_interface_usable | ||
</e> | </e> | ||
+ | |||
+ | as the first assertion. Add any other preconditions for the event handler's arguments known to hold true when an event is published. | ||
+ | |||
+ | === Supplying a Publisher Object === | ||
+ | A common ideal when creating an event is not just to pass the event data but also the object for on which the event was published for. This may be a requisite piece of event data needed by an observer descendant to actually handle the event correctly. When subscribing to events explicitly, one may use an <e>agent</e> to supply the event host object to the event handler. When using an observer this information is lost when publishing the event. | ||
+ | |||
+ | Changing the original <e>changed_event</e> declaration to include and event host (sender) will permit observers to receive the event host object. | ||
+ | |||
+ | <e> | ||
+ | class | ||
+ | MY_INTERFACE_I | ||
+ | |||
+ | .... | ||
+ | |||
+ | feature -- Events | ||
+ | |||
+ | changed_event: !EVENT_TYPE_I [TUPLE [sender: MY_INTERFACE_I; flags: INTEGER]] | ||
+ | .... | ||
+ | </e> | ||
+ | |||
+ | {{Recommended|The event host argument should be the first event argument.}} | ||
+ | |||
+ | The observer must also be updated to receive the extra argument. | ||
+ | |||
+ | <e> | ||
+ | class | ||
+ | MY_INTERFACE_OBSERVER | ||
+ | |||
+ | ... | ||
+ | |||
+ | feature -- Event Handler | ||
+ | |||
+ | on_changed (a_sender: MY_INTERFACE_I; a_flags: INTEGER) | ||
+ | require | ||
+ | is_interface_usable: {l_usable: USABLE_I} Current implies l_usable.is_interface_usable | ||
+ | a_sender_is_interface_usable: a_sender.is_interface_usable | ||
+ | is_valid_flags_a_flags: a_sender.is_valid_flags (a_flags) | ||
+ | ... | ||
+ | </e> | ||
+ | |||
+ | In addition to the extra argument, a new set of preconditions can be added to better ensure correctness. | ||
+ | |||
+ | ==== Create an Observer Connection Point ==== | ||
+ | The second step to supporting an interface's events is to provide access to a event connection point. A connection is the façade 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 versatile model for observers. | ||
+ | |||
+ | A connection point is an attribute or a function, in the interface class, with a result type of <e>EVENT_CONNECTION_I</e>. <e>EVENT_CONNECTION_I</e> requires two formal generic parameter the former being the name of the observer class (<e>INTERFACE_OBSERVER</e>) and the latter the interface containing the events. | ||
+ | |||
+ | {{Warning|Do not use <e>like Current</e> when refering to the interface name in the formal generic parameter. This will only cause CAT-calls and headaches!}} | ||
+ | |||
+ | Again, the routine in the interface should be deferred and not just for implementation specific freedom but also for memory management. | ||
<e> | <e> | ||
Line 62: | Line 133: | ||
INTERFACE_I | INTERFACE_I | ||
− | + | ... | |
− | + | ||
feature -- Access | feature -- Access | ||
− | interface_connection: EVENT_CONNECTION_I [INTERFACE_OBSERVER, INTERFACE_I] | + | interface_connection: !EVENT_CONNECTION_I [INTERFACE_OBSERVER, INTERFACE_I] |
-- Connection point for {INTERFACE_I} events. | -- Connection point for {INTERFACE_I} events. | ||
require | require | ||
is_interface_usable: is_interface_usable | is_interface_usable: is_interface_usable | ||
+ | deferred | ||
+ | ensure | ||
+ | result_is_interface_usable: Result.is_interface_usable | ||
+ | end | ||
+ | |||
+ | feature -- Events | ||
+ | |||
+ | ... | ||
+ | |||
+ | end | ||
+ | </e> | ||
+ | |||
+ | {{Recommended|Again, there is a recommended rule to naming here. It is not recommended to simply call a connection access attribute/function <e>connection</e>. 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. | ||
+ | <br/><br/> | ||
+ | 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 <e>_connection</e>. Again, like the observer's class name, remove the <e>_I</e> (or <e>_S</e>) suffix from the class name. Of course, it goes without saying that the name should be in lower case.}} | ||
+ | |||
+ | In the effective implementation, fully or otherwise, | ||
+ | |||
+ | <e> | ||
+ | class | ||
+ | INTERFACE | ||
+ | |||
+ | inherit | ||
+ | DISPOSABLE_SAFE | ||
+ | |||
+ | INTERFACE_I | ||
+ | |||
+ | ... | ||
+ | |||
+ | feature -- Access | ||
+ | |||
+ | interface_connection: !EVENT_CONNECTION [INTERFACE_OBSERVER, INTERFACE_I] | ||
+ | -- <Precursor> | ||
local | local | ||
l_target: INTERFACE_OBSERVER | l_target: INTERFACE_OBSERVER | ||
attribute | attribute | ||
+ | -- Create the event-action bindings | ||
create l_target | create l_target | ||
− | create | + | create Result.make_from_array (<< |
− | + | [changed_event, agent l_target.on_changed] | |
− | + | >>) | |
− | + | auto_dispose (Result) | |
− | + | ||
end | end | ||
− | + | ... | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
end | end | ||
</e> | </e> | ||
+ | |||
+ | In the implementation of <e>interface_connection</e> the Result type has been covariantly redefine to the implementation event connection type <e>EVENT_CONNECTION</e>. This is not necessary as EVENT_CONNECTION_I and EVENT_CONNECTION both have the same exported interface for all intents and purposes. <e>EVENT_CONNECTION</e> actually implements <e>DISPOSABLE_SAFE</e>, which in turn implements <e>DISPOSABLE_I</e> and <e>DISPOSABLE</e>. This supports the dispose pattern used through ESS. | ||
+ | |||
+ | At the end of the self-initializing attribute the event connection point is registered to be automatically cleaned up when the interface object is cleaned up. Disposing of an <e>EVENT_CONNECTION</e> object will unsubscribe all subscription to the interface's events and remove all connected observers. | ||
== Event Connections and Observers == | == Event Connections and Observers == | ||
To Su | To Su |
Latest revision as of 16:12, 24 December 2008
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.
Contents
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 MY_INTERFACE_I inherit INTERFACE_I feature -- Events changed_event: !EVENT_TYPE_I [TUPLE [sender: MY_INTERFACE_I; 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 event handlers 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, using a lazy-instantiation model (the latter being commonly used for efficient memory management and clean-up) or a façade to another interface.
Finally, the Result
type of the event feature is EVENT_TYPE_I
and not the more specific implementation type EVENT_TYPE
. As previously mentioned, the latter effective type contains means to publish the events, which should not be accessed by any client of the interface.
Supporting Events
With such a diminutive example there seems no need for any support for a single event, when using multiple events it becomes more evident and necessary to support the interface. Eventing support comes in the form of providing an implementation observer pattern, which can autonomously subscribe 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 un-subscribed to all subscribed 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: 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 MY_INTERFACE_OBSERVER inherit EVENT_OBSERVER_I feature -- Event Handler on_changed (a_sender: MY_INTERFACE_I; a_flags: INTEGER) -- require is_interface_usable: {l_usable: USABLE_I} Current implies l_usable.is_interface_usable a_sender_is_interface_usable: a_sender.is_interface_usable is_valid_flags_a_flags: a_sender.is_valid_flags (a_flags) do end end The observer inherits <e>EVENT_OBSERVER_I, an interface class currently containing no declarations. It's used currently for identity and potential future additions required to support observing events.
EVENT_OBSERVER_I
does not use the usable pattern and so access to the queiries of USABLE_I
are unavailable. However, in ESS it is very likely a descenent of the observer will implement USABLE_I
and should be supported regardless of EVENT_OBSERVER_I
's hierarchy. Be sure to add the precondition
is_interface_usable: {l_usable: USABLE_I} Current implies l_usable.is_interface_usable
as the first assertion. Add any other preconditions for the event handler's arguments known to hold true when an event is published.
Supplying a Publisher Object
A common ideal when creating an event is not just to pass the event data but also the object for on which the event was published for. This may be a requisite piece of event data needed by an observer descendant to actually handle the event correctly. When subscribing to events explicitly, one may use an agent
to supply the event host object to the event handler. When using an observer this information is lost when publishing the event.
Changing the original changed_event
declaration to include and event host (sender) will permit observers to receive the event host object.
class MY_INTERFACE_I .... feature -- Events changed_event: !EVENT_TYPE_I [TUPLE [sender: MY_INTERFACE_I; flags: INTEGER]] ....
Recommended: The event host argument should be the first event argument.
The observer must also be updated to receive the extra argument.
class MY_INTERFACE_OBSERVER ... feature -- Event Handler on_changed (a_sender: MY_INTERFACE_I; a_flags: INTEGER) require is_interface_usable: {l_usable: USABLE_I} Current implies l_usable.is_interface_usable a_sender_is_interface_usable: a_sender.is_interface_usable is_valid_flags_a_flags: a_sender.is_valid_flags (a_flags) ...
In addition to the extra argument, a new set of preconditions can be added to better ensure correctness.
Create an Observer Connection Point
The second step to supporting an interface's events is to provide access to a event connection point. A connection is the façade 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 versatile model for observers.
A connection point is an attribute or a function, in the interface class, with a result type of EVENT_CONNECTION_I
. EVENT_CONNECTION_I
requires two formal generic parameter the former being the name of the observer class (INTERFACE_OBSERVER
) and the latter the interface containing the events.
Warning: Do not use like Current
when refering to the interface name in the formal generic parameter. This will only cause CAT-calls and headaches!
Again, the routine in the interface should be deferred and not just for implementation specific freedom but also for memory management.
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 deferred ensure result_is_interface_usable: Result.is_interface_usable end feature -- Events ... end
Recommended: 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.
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.
In the effective implementation, fully or otherwise,
class INTERFACE inherit DISPOSABLE_SAFE INTERFACE_I ... feature -- Access interface_connection: !EVENT_CONNECTION [INTERFACE_OBSERVER, INTERFACE_I] -- <Precursor> local l_target: INTERFACE_OBSERVER attribute -- Create the event-action bindings create l_target create Result.make_from_array (<< [changed_event, agent l_target.on_changed] >>) auto_dispose (Result) end ... end
In the implementation of interface_connection
the Result type has been covariantly redefine to the implementation event connection type EVENT_CONNECTION
. This is not necessary as EVENT_CONNECTION_I and EVENT_CONNECTION both have the same exported interface for all intents and purposes. EVENT_CONNECTION
actually implements DISPOSABLE_SAFE
, which in turn implements DISPOSABLE_I
and DISPOSABLE
. This supports the dispose pattern used through ESS.
At the end of the self-initializing attribute the event connection point is registered to be automatically cleaned up when the interface object is cleaned up. Disposing of an EVENT_CONNECTION
object will unsubscribe all subscription to the interface's events and remove all connected observers.
Event Connections and Observers
To Su