Event Notifier, a Pattern for Event Notification

An object behavioral design pattern for general-purpose type-based event notification.


Suchitra Gupta
Jeff Hartkopf
Suresh Ramaswamy

This article was originally published in Java Report, July 1998, Volume 3, Number 7, by SIGS Publications.


In our complex world, events are constantly occurring. Any one person is only interested in a very small subset of all these events, so humans have worked out ways of getting just the information of interest, which works to a degree. We may periodically check to see if the event has occurred, or we ask someone to notify us when the event occurs. Often there is more than one source of a particular type of event such as disaster related events, but we do not typically care about who notifies us, just that the event has occurred. Ideally, we would subscribe to just those types of events in which we are interested, and be notified of them when they occur.

Event notification is a useful communication paradigm in computing systems as in real life. This article documents general event notification in the form of a design pattern, which provides a useful way of exposing readers to the important concepts and issues involved in event notification, and provides a language-neutral pattern for implementing event notification in a variety of scenarios. Concurrently, we discuss design issues using examples as appropriate to demonstrate effective use of event notification. Diagrams use the Unified Modeling Notation (UML) [1].


Intent

Enable components to react to the occurrence of particular events in other components without knowledge of one another, while allowing dynamic participation of components and dynamic introduction of new kinds of events.


Also Known As

Dispatcher, Decoupler, Publish-Subscribe


Motivation

To understand the need for Event Notifier, we will take a simple example of a network management system, and implement it in a simplistic way. Then we will look at the problems with this approach, and incrementally show how we might solve them using Event Notifier.

Consider a large network of distributed components, such as computers, hubs, routers, software programs, and so forth. We wish to monitor and manage these components from a central location. We will refer to the components being managed as managed objects. Problems are typically infrequent and unpredictable, but when they do occur we wish to be notified without having to constantly poll the managed objects. The notification may appear on a management system such as a central console, pager, or electronic mail reader. For our example, suppose we have both a console and a paging system. In the simplistic implementation, shown in Figure 1a, a managed object must send notification of problems to both the console and the paging system. If we later wish to change the interface to the console or paging system, or add an electronic mail system, every managed object must be modified. Apart from being unscalable, this approach is very error prone, since each managed object must essentially duplicate the same sequence for notification, making consistency difficult to achieve. Encapsulating the notification behavior in a common superclass only partially mitigates the problem.

(a) Simplistic Approach
(b) Mediator Approach
(c) Observer Approach
(d) Event Notifier Approach
Figure 1: Approaches to a Network Management System

In a system of any size, we would like to minimize the number of dependencies and interconnections between objects to keep the system from becoming brittle and hard to change. The more dependencies there are, the more a change in any particular point in the system propagates to other points in the system. The simplistic approach requires each managed object to maintain a reference to each management system. The number of these references increases geometrically with the number of managed objects and management systems. A better approach that keeps this to a linear increase is to have a mediator that encapsulates and coordinates the communication, as shown in Figure 1b. To report a problem, each managed object notifies the mediator, which in turn notifies the console and paging system as appropriate. Now, to modify an existing management system or add a new one, such as an electronic mail system, we need only to modify the mediator. This is a use of the Mediator design pattern [2].

An alternative approach to solving the dependency problem is to introduce the notion of notification to the system in a generic way, using the Observer design pattern [2]. As shown in Figure 1c, each managed object implements a common "subject" interface which allows interested observers such as the paging system and console to register interest in its events. When a managed object has a problem to report, it traverses its internal list of interested observers, calling each in turn. Unlike in the simplistic approach, the managed object does not need to know a priori which systems to notify; the management systems themselves are responsible for dynamically registering with the managed objects for notification. However, we have introduced a new problem: now the management systems need to know about each managed object, to register interest in them. If anything, this is worse, because there may be an arbitrarily large number of managed objects on a large network, and they may come and go frequently. We need a mechanism that requires neither the managed objects nor the management system to have a priori knowledge of each other, but to be able to still communicate problems.

The Observer approach has the benefit of allowing more dynamic behavior than the Mediator approach: new management systems may be added without impacting the rest of the system, although we cannot do the same with managed objects. It also does not require the presence of an omniscient mediator that understands and controls the flow of interactions: behavior that naturally fits in the managed objects or management systems may stay there. However, each subject has the burden of maintaining a list of observers and calling them as necessary, which the mediator approach nicely centralizes. It is possible to implement the system in a way that combines the benefits of both the Mediator and Observer approaches, as shown in Figure 1d. Like in the Mediator approach, we have a central event service that mediates notification, so that managed objects and management systems do not need to know about each other. Like in the Observer approach, a registration system allows us to add and remove observers (called subscribers) dynamically. Unlike the Observer approach, however, this functionality is centralized in the event service, relieving subjects (called publishers) of this burden. We give the name Event Notifier to this best-of-both-worlds approach.

Event Notifier derives many of its benefits from the fact that subscribers only know about events, not about publishers. For example, routers and hubs might both generate events of the same type, say FaultEvent, when problems occur. In an Observer implementation, each management system needs to know which managed objects generate fault events and register with each. The same is essentially true of Mediator, except that the mediator encapsulates this knowledge. However, using Event Notifier, a management system needs only to register interest in the FaultEvent type to get all fault events, regardless of who publishes them.


Applicability

Use the Event Notifier pattern in any of the following situations:


Structure

The class diagram in Figure 2 shows the structure of the Event Notifier pattern. For more detail on the purpose of each class and the interactions between them, see the next two sections.

Figure 2: Event Notifier Structure

The EventService class contains the aggregations filters, subscribers, and eventTypes. Although not readily apparent from the structure, corresponding elements from these aggregations comprise a tuple. Each tuple corresponds to the parameters passed to a single call to subscribe.


Participants

This section describes the responsibilities of the classes shown in the Event Notifier structure.


Collaborations

The collaboration diagram in Figure 3 shows the typical sequence of interactions between participating objects.

Figure 3: Event Notifier Collaboration Diagram

The subscriber invokes the subscribe method on the event service (1) specifying the event type it is interested in and passes a reference to itself (or possibly another object) and a filter. The eventType argument represents the type of the event. When an event occurs, the publisher invokes the publish method on the event service (2) passing an event object. The event service determines which subscribers are interested in this event, and for each of them applies the filter (3) provided by the subscriber. If no filter is provided, or if application of the filter results in true, then the event service invokes the inform method of the subscriber (4), passing the event as an argument.

Notice that all publication and subscription is done through the event service. The event service maintains all information about which subscribers are interested in which events, so that publishers and subscribers need not be aware of each other. Moreover, anyone may publish or subscribe to events using the well-defined interface provided by the event service without having to implement any special logic to handle interaction with other entities for which it has no other reason to communicate.


Consequences

This section discusses the results and tradeoffs associated with the use of Event Notifier.

Subscription is based on event type rather than publisher. In some event notification models, a subscriber registers interest in receiving events from a particular publisher or type of publisher. This is useful when subscribers have knowledge of the publisher. When processing events from a graphical user interface (GUI), for example, the event handler knows about the individual controls that can publish events. In Event Notifier, a subscriber registers interest based solely on event type, without regard to publisher. This is more suitable for services in a distributed environment that are not coupled and may cooperate using an enterprise-wide event hierarchy without any knowledge of each other at compile time or run time. For those cases where subscribers are interested in events from a particular publisher, include an event attribute that identifies the source, and define a filter that uses this attribute to discard uninteresting events.

Subscribing to an event type automatically subscribes to all its subtypes. Because event types are structured in an inheritance hierarchy, when a subscriber subscribes to a particular event type, the event service notifies it of all events of that type or any subtype in the hierarchy. This enables subscribers to specify interest in events as broadly or narrowly as necessary. This feature is easier to implement in languages like Java and Smalltalk that provide rich run-time type information.

Events can be filtered. Filtering allows a subscriber to programmatically select the events of interest. By specifying an event type at subscription time, the subscriber narrows its interest to a certain class of events. An explicit filter allows further selection of events prior to notification, based on custom criteria such as the values of certain event attributes. For example, a filter might use an event source attribute to restrict events to those from a certain source. In the network management example described earlier, regional monitoring centers could use filters to limit events to those received from the regions of interest.

Subscribers and publishers can vary independently. Event subscribers and publishers do not have knowledge of each other and can vary independently. The understanding between the two is via agreement on a set of legal event types, the semantics of what an event means, and event data associated with an event type.

There can be multiple publishers and subscribers for a given kind of event. Some patterns for event notification require a subscriber to "know" each publisher of an event type. This can be difficult or impossible if the publishers of an event cannot be determined at compile time.

Subscribers can be interested in events for a limited duration. They can subscribe and unsubscribe at will. Support for dynamic registration and unregistration allows for this freedom.

Subscribers and publishers may be transient. A new subscriber or publisher can appear or disappear without impacting other components of the system. This is particularly important because it allows relocation of services in a distributed environment.

Event types can be introduced with minimal impact. In languages like Java that support dynamic loading of classes, one can add new event types to an existing application dynamically. Existing subscribers will receive events of the new event type if they are already subscribing to a supertype. One can dynamically add publishers for the new event type without having to rebuild the entire application. In environments without support for dynamic class loading, one may need to rebuild the application to add new event types, but no changes to subscriber or event service code are required.

Event Notifier makes a tradeoff between genericity and static type safety. In large distributed systems, change can entail recompilation and redistribution of many components to multiple locations in a network. It is imperative to design such systems to be resilient to change, minimizing the impact of change to the smallest number of components possible. Using Event Notifier helps accomplish this goal by keeping components decoupled from the beginning and allowing for change by keeping interfaces generic in terms of the types they deal with. However, this genericity and extensibility comes at the cost of some type safety provided by compile-time type checking. If the same event can emanate from one of many sources then this flexibility pays off. The Reclaiming Type Safety subsection under Implementation describes one way to mitigate the lack of static type checking for events.

The event service could be a single point of failure or bottleneck. This could be the case if a single event service brokers all events. However, we can mitigate these and other problems associated with centralization by distributing the work without changing the semantics, as discussed in the Enhancing Fault Tolerance and Performance section of Implementation.


Implementation

This section discusses specific issues that one must address when implementing Event Notifier in a real situation. We divide the issues into more or less autonomous sections, discussing implementation techniques where appropriate.

Accessing the Event Service

An event service always has a well-known point of access so that subscribers and publishers in a given environment can share the same event service. One can provide this well-known point of access using the Singleton or Abstract Factory pattern [2], or by registering the event service with a well-known naming service.

Modeling Events

When modeling events, one must think about how to decompose communication in a system into generic messages that convey certain standard information in the form of an event object. Such an object could be of any type, but there are certain advantages to requiring event types to be subclasses of an Event base class as shown in Figure 4. One advantage is that the Event class can enforce a common interface or set of attributes on all events, such as a time stamp which captures the time of occurrence of an event. Such an event hierarchy also enables type-based subscription: subscribers can specify a non-leaf node in the event tree to be informed of all events of that type or below.

Figure 4: Example Event Hierarchy

The natural way to implement an event hierarchy in an object-oriented language is by using inheritance. However, it could also be implemented using a string representation, with the levels of the hierarchy separated by colons. For example, the event types in Figure 4 might be represented as Event, Event:ManagementEvent, Event:ManagementEvent:FaultEvent, and so forth. The inheritance mechanism, not surprisingly, has several advantages. Principally, it allows an event subscriber to more easily subscribe to all events in a subtree of the type hierarchy. One subscriber may want all management events, while another may only be interested in data events. This is expressed simply by subscribing to the event at the root of the hierarchy of interest. Furthermore, since events are objects, attributes may be packaged naturally with them, so the subscriber need not invoke methods on (say) the publisher to retrieve those attributes. The inheritance mechanism also takes advantage of compile-time type checking. The compiler will not catch errors in string representations, such as a misspelled event type, but it will catch errors in class names. The advantages to the string technique are that it works in languages that are not object-oriented, and strings may be generated dynamically, while in many languages, objects in an event hierarchy must have classes that are defined at compile time.

Events can have various characteristics, which may lead to alternative ways of arranging them into a single hierarchy. Figure 5 shows two ways to model management events. To take advantage of type-based event notification, it is useful to choose a model based on the kind of events in which one anticipates subscribers having interest. If subscribers may need to be informed of all fault events, regardless of whether they are hardware or software faults, then modeling the events as in Figure 5a would be appropriate. On the other hand, if subscribers are interested in all hardware events regardless of whether they are fault or performance events, devising an event hierarchy as shown in Figure 5b makes sense.

(a) Alternative 1
(b) Alternative 2
Figure 5: Alternative Event Hierarchies

Event characteristics may appear as attributes of events instead of being modeled as types in the event hierarchy. However, this approach prevents subscription based on that characteristic. Filters can be used to achieve a similar effect, although they are less efficient and not as type safe.

Identification of event source (publisher) is useful in certain situations. As alluded to earlier, when processing events from a GUI, the action taken by a subscriber in response to an event may depend on the source of the event. On a button click event, the subscriber would want to known which button was clicked. Event source could also be used to advantage in situations where one would otherwise need to specify a number of attributes that have information related to the source. In this case, storing an event source handle as an event attribute will allow a subscriber to get the information it needs about the event source from the event source directly. The tradeoff is that including the event source as an attribute leads to coupling between subscriber and publisher.

Determining Event Type at Run Time

To subscribe to events of a particular type, a subscriber passes an instance of a metaclass. When an event is published, the event service checks whether any subscribers are interested in it. Different implementation languages support the notion of a metaclass and the ability to check if an object is of a certain type to varying degrees.

In Java, the Class class is like a metaclass. One can check whether an object is of a certain type by invoking the isAssignableFrom method on the type's Class object, passing the Class object for the object in question. If an instance of the class specified as an argument to isAssignableFrom could be assigned to an instance of the class on which this method is invoked, then the class specified in the argument must be the same class as, or a subclass of, the class on which the method is invoked. This is the essential mechanism for supporting type-based notification in Event Notifier.

Smalltalk supports a similar mechanism as part of its Class class for determining if an object is of a certain type.

C++ also provides run-time type information. To check if an event object is of a certain event type, one might think of using the dynamic_cast operator. However, this is not sufficient, since the event service only knows the event type to which to cast at run time. We would prefer to implement the event service generically, without the need to modify it each time we use it in a different environment with a different set of event types. Even in a single environment, we would prefer not to have to alter the implementation of the event service whenever we introduce a new event type, so as to promote reuse.

A second alternative for C++ uses the type_info class as an approximation of a metaclass. Using the typeid operator, the event service can check whether an event is of the same type as that specified by the type_info object. This technique, however, does not fully address the requirement since the published event could be a subtype of the type in the type_info object. Dealing with this issue requires type information beyond that supported in C++. However, there are well-known ways to accomplish getting the additional type information. One technique is for each class in the event hierarchy to implement an isKindOf method. The NIH class library [3] provides a good example of how this can be done. Every NIH class has a private static member variable named classDesc that is an instance of a class named Class. This class descriptor object contains items of information about an object's class such as its name and superclass(es). Objects instantiated from NIH classes support an isKindOf method that in turn relies on information in the corresponding Class object to ascertain whether an object is of a certain type.

Reclaiming Type Safety

As mentioned in Consequences, the genericity of Event Notifier comes at the expense of some type safety. This problem manifests itself in the implementation of the subscriber's inform method and the filter's apply method. Below, we describe the problem and potential solutions in terms of the subscriber, but the same applies to the filter.

The subscriber receives an event of type Event, which it must downcast to the type of event it is expecting. If a subscriber subscribes to more than one type of event, it first needs to check the event type. Even if it subscribes to only a single event type, it should first validate the event type to prevent run-time errors due to improper coding. For example, the subscriber may inadvertently have given the wrong event type at the time of subscription.

In a language like Java, it is possible to eliminate the need for each subscriber to downcast by relying on the extensive run-time type information and the reflection API provided by the language. For each event type to which a concrete subscriber subscribes, it implements a type-specific inform method that takes as an argument an event of that type. The abstract Subscriber class implements an inform method that takes a parameter of type Event. When an event occurs, the event service calls this inform method. The inform method uses the reflection API to invoke the appropriate type-specific inform method provided by the concrete subscriber, passing the event as an argument. If there is no appropriate type-specific inform method, the abstract subscriber throws an exception. Alternatively, this error could be caught even earlier by the event service at subscription time. Figure 6 illustrates a paging system that subscribes to two event types and provides type-specific inform methods to handle them. Note that the concrete subscriber must not override the inform method that takes an Event type as a parameter, since this will neutralize the dispatching mechanism described.

Figure 6: Type Safety

Advertisement and Discovery

In some situations, it may be useful for the subscriber to be able to query the event service as to which event types are currently available. For example, a management console might present a list of all possible event types to the user, allow the user to pick those of interest, and then subscribe to those events on the user's behalf.

Support for this capability requires a simple modification to Event Notifier. Publishers could explicitly advertise the types of events they can publish with the event service by calling an advertise method, and the event service could provide an interface that allows subscribers to discover the list of advertised events.

Distributed Event Notification

The Event Notifier pattern is well suited to a distributed implementation, where publishers, subscribers, and the event service itself reside on distinct machines. A distributed system can realize additional benefits by using Event Notifier in conjunction with the Proxy pattern [2], as shown in Figure 7.

Figure 7: Event Proxy Structure

The RemoteEventService is an event service in a distributed environment. An EventProxy resides locally and serves as a proxy for the remote event service, and is implemented as a singleton. It maintains a list of local subscriptions, and subscribes to events from the remote event service on behalf of the local subscribers.

For simplicity, we show the common EventService superclass maintaining the aggregations. It may be necessary to actually implement EventService as an abstract class, with the aggregations implemented in the subclasses RemoteEventService and EventProxy. For example, the event proxy may choose not to maintain a list of subscribers, but to delegate all operations directly to the remote event service. Alternatively, the type of objects stored in the aggregations may be different for the event proxy and the remote event service: the former may store local subscribers, and the latter may store remote subscribers (proxies).

Figure 8 shows a typical interaction between the various objects.

Figure 8: Event Proxy Collaboration Diagram

Use of an event proxy insulates publishers from the issues that arise in a distributed environment. In particular, an event proxy can yield the following benefits:

Sometimes certain events are only of interest in a local environment, and others are of interest to the entire remote community. For example, GUI events are typically of interest locally, while fault events are of interest remotely. An event proxy can double as a standalone local event service if there is some way to specify whether to publish an event remotely or locally. One can specify this as an event attribute or as part of the event type hierarchy. The former allows the programmer to determine how to use the event, while the latter allows the designer of the event hierarchy to enforce this as part of the design. If only local events are published and subscribed to, the proxy can function in the absence of the remote event service.

Enhancing Fault Tolerance and Performance

Large scale use of Event Notifier can potentially result in the event service being a single point of failure and a performance bottleneck. One solution to this problem is to have multiple event services, where each brokers a group of related events; for example, one event service for network management, and another for financial services. The failure of the event service for one area will not impact the systems dependent only on events from the other area.

Another way to deal with the problem is to use a variation of Event Notifier in which the subscription list is not stored in the event service, but is instead distributed across publishers. One can achieve this without breaking the semantics of a centralized event service by having publishers advertise the event types they intend to publish. The event service would maintain this association of publishers and event types. On subscription to an event type, the event service would pass the subscriber to the publishers of that event type. Now publishers can inform subscribers directly.

Since the subscription list is distributed among the set of publishers instead of being stored centrally in the event service, failure of any single component is likely to have less impact on the system as a whole. Since the frequency of publication is typically much higher than the frequency of subscription, and at publication time events are not being channeled through a single point, there is no single point that could become a bottleneck.

Since the publication list changes less frequently than the subscription list, it is feasible to maintain a "hot spare" for the event service. Whenever an advertisement occurs, the event service updates the hot spare, thus keeping the information current in both places. If the event service goes down, the hot spare can take over.

Asynchronous Notification

If one implements the event service as a synchronous service, when a publisher publishes an event, the event service returns control to the publisher only after it informs each of the interested subscribers. If there are a large number of subscribers, or if subscribers do not return control to the event service in a timely manner, the publisher is blocked, and the event service cannot process other requests. One can fix this problem by providing asynchronous notification. For example, the event service could use a separate thread to handle the publication, and return control to the publisher immediately. The event service could start a separate thread to inform each subscriber, or rely on a thread pool.

Push and Pull Semantics

Event Notifier supports the canonical push model semantics [4], in which the publisher pushes events to the event service, and the event service pushes events to the subscriber. It is possible to have an active subscriber that pulls information from the event service, or a passive publisher from which information is pulled by the event service. If a pull model is desired at either leg, event notification semantics can be reestablished from the user's point of view by hiding it behind a proxy.

Subscriber Anchoring

Generally in distributed environments, the subscriber executes the response to an event within its own address space. We refer to this type of subscriber as anchored, and it passes its own remote handle to the event service at subscription time. At other times, it is not important where the inform method is executed. An example of such an unanchored subscriber is one that pages an appropriate person, which can execute on its own. An unanchored subscriber passes a copy of itself to the event service, and the event service executes the inform method in its own address space. The process that called subscribe no longer needs to exist to process the events. An unanchored subscriber acts as an agent, which takes action in response to events independently of the process that called subscribe.


Sample Code

This section provides a skeletal implementation of Event Notifier in Java, and shows how it can be used.

Event Service

The EventService class is implemented as a singleton, providing a well-known point of access and implementations for publish, subscribe, and unsubscribe.

public class EventService {
  // Prevents direct instantiation of the event service
  private EventService() {
    eventClass = Event.class;
    subscriptions = new Vector();
  }

  static private EventService singleton = null;
 
  // Provides well-known access point to singleton EventService
  static public EventService instance() {
    if (singleton == null)
      singleton = new EventService();
    return singleton;
  }

  public void publish(Event event) {
    for (Enumeration elems = subscriptions.elements(); elems.hasMoreElements(); ) {
      Subscription subscription = (Subscription) elems.nextElement();
      if (subscription.eventType.isAssignableFrom(event.getClass())
          && (subscription.filter == null || subscription.filter.apply(event)))
        subscription.subscriber.inform(event);
    }
  }

  public void subscribe(Class eventType, Filter filter, Subscriber subscriber)
    throws InvalidEventTypeException {
      if (!eventClass.isAssignableFrom(eventType))
        throw new InvalidEventTypeException();
 
      // Prevent duplicate subscriptions
      Subscription subscription = new Subscription(eventType, filter, subscriber);
      if (!subscriptions.contains(subscription))
        subscriptions.addElement(subscription);
  }

  public void unsubscribe(Class eventType, Filter filter, Subscriber subscriber)
    throws InvalidEventTypeException {
      if (!eventClass.isAssignableFrom(eventType))
        throw new InvalidEventTypeException();
      subscriptions.removeElement(new Subscription(eventType, filter, subscriber));
  }

  private Class eventClass;
  protected Vector subscriptions;
}

// Stores information about a single subscription
class Subscription {
  public Subscription(Class anEventType, Filter aFilter, Subscriber aSubscriber) {
    eventType = anEventType;
    filter = aFilter;
    subscriber = aSubscriber;
  }

  public Class eventType;
  public Filter filter;
  public Subscriber subscriber;
}

public class InvalidEventTypeException extends RuntimeException {}

Event

The interface construct in Java is used to implement the abstract Event class shown in the Event Notifier structure.

public interface Event {}
public interface ManagementEvent extends Event {}

public class FaultEvent implements ManagementEvent {
  public static final int CRITICAL = 1;
  public static final int MODERATE = 2;
  public static final int LOW = 3;

  public int severity;

  FaultEvent() {
    severity = LOW;
  }

  FaultEvent(int aSeverity) {
    severity = aSeverity;
  }
}

Filter

When the apply method on the CriticalFaultFilter class is called with an event, it returns true if the event is a critical fault event. A subscriber that provides this filter thus manages to filter out all but the critical fault events.

public interface Filter {
  public boolean apply(Event event);
}

public class CriticalFaultFilter implements Filter {
  public boolean apply(Event event) {
    // Assumes that this filter is used only with subscriptions to FaultEvent
    FaultEvent faultEvent = (FaultEvent) event;
    return (faultEvent.severity == FaultEvent.CRITICAL);
  }
}

Subscriber

An abstract subscriber class and an example concrete subscriber are shown below. The PagingSystem subscriber subscribes to and handles FaultEvent.

public abstract class Subscriber {
  abstract void inform(Event event);
}

public class PagingSystem extends Subscriber {
  public PagingSystem() {
    FaultEvent event = new FaultEvent();
    CriticalFaultFilter filter = new CriticalFaultFilter();
    EventService.instance().subscribe(filter.getClass(), filter, this);
  }

  public void inform(Event event) {
    // Assumes that this subscriber has only subscribed to FaultEvent
    FaultEvent faultEvent = (FaultEvent) event;

    // Respond to the event
    System.out.println("Critical Fault Event occurred:" + faultEvent);
  }
}

Publisher

The Router class is an example publisher, which publishes a critical fault event.

public class Router {
  public static void triggerPublication() {
    Event event = new FaultEvent(FaultEvent.CRITICAL);
    EventService.instance().publish(event);
  }
}

Using Dynamic Typing to Restore Type Safety

As described in the Reclaiming Type Safety subsection of Implementation, it is possible for the abstract subscriber's inform method to determine at run time which inform method of a concrete subscriber to invoke using the Java reflection API. Notice that the PagingSystem provides a type-specific inform method for handling fault events which does not need to downcast the event.

public abstract class Subscriber {
  public void inform(Event event) {
    Class paramTypes[] = new Class[1];
    paramTypes[0] = event.getClass();
    try {
      // Look for an inform method in the current object
      // that takes the event subtype as a parameter
      Method method = getClass().getDeclaredMethod("inform", paramTypes);
      Object paramList[] = new Object[1];
      paramList[0] = event;
      method.invoke(this, paramList);
    }
    // Catch appropriate exceptions
  }
}

public class PagingSystem extends Subscriber {
  public void inform(FaultEvent event) {
    // Handle the fault event with no need to downcast
  }
}


Known Uses

In this section, we compare two other event notification models with the Event Notifier model.

JavaBeans

The JavaBeans API defined as part of the Java platform introduces reusable software components called JavaBeans. The user can connect and manipulate beans programmatically or visually using a bean builder. Beans communicate with each other via event notification. JavaBeans event notification is type safe, instance based, synchronous, and multicasting. It is restricted to a single process. For every event, there is a corresponding EventListener interface. The event source (publisher) advertises one or more EventListener interfaces that EventListener beans (subscribers) implement to receive events directly from the source beans. Source and listener beans know about each other, and the source handle is always passed as part of an event. Each source bean is responsible for maintaining a list of interested listeners. In Event Notifier, a well-known event service takes on this responsibility. Using Java's introspection API, beans can discover listener interfaces advertised by a source and implemented by a listener. Unless one uses an event adapter, the subscriber and publisher are coupled.

CORBA

The CORBA Event Service [4] provides a way of delivering event data from supplier (publisher) to consumer (subscriber) without them knowing about each other. It supports typed and untyped event systems, neither of which allows events as objects.

The CORBA Event Service supports both pull and push models, which allow suppliers and consumers to play active or passive roles. This results in unnecessary complexity. Furthermore, the CORBA Event Service provides no filtering.


Related Patterns

This section compares and contrasts other closely related patterns with Event Notifier.

Observer and Mediator

As mentioned in the Motivation section, Event Notifier is closely related to the Observer and Mediator patterns [2], and in fact, can be viewed as a cross between them. Table 1 summarizes similarities and differences among the patterns.

Mediator Observer Event Notifier
Knowledge of who can publish and whom to notify of events Localized in the mediator Duplicated by each observer and subject Localized in the event service
Coordination of behavior in response to events Managed exclusively by the mediator Managed by participating observers Managed by participating subscribers
Messaging flexibility Application-specific messages through methods defined at compile time Single generic message through the predefined update() method Flexible messaging by using different event types
Informative content of messages Methods may be defined to contain information as desired Predefined update() method has no arguments, so no information is carried in the message Events may be defined to contain information as desired
Registration of interest in notification Interest in notification is embedded in the mediator Observers may register interest dynamically Subscribers may register interest dynamically
Coupling between publishers and subscribers Mediator decouples colleagues Observers and subjects are coupled Event service decouples publishers and subscribers
Relationship between publishers and subscribers Many-to-many One-to-many, as an observer may only observe a single subject Many-to-many
Table 1: Comparison with Mediator and Observer

The Multicast Pattern

The Multicast pattern [5] is a pattern in progress that is in some ways similar to Event Notifier. At the danger of comparing Event Notifier with a pattern still in flux, we examine some of the differences between the two patterns.

The differences arise from the different tradeoffs that have been made which are reflected in their corresponding applicabilities. In particular, the main goal of Multicast is to retain static type checking, while Event Notifier emphasizes genericity.

Multicast is decentralized. Every sender (publisher) keeps track of who is interested in the events it generates. Subscribing to an event generated by a sender requires invoking a method that takes a parameter of a type specific to that event. Receivers (subscribers) have methods to deal with specific events and have the specific event passed in as a parameter. There are different methods on senders that accept different handler types for different events. Defining a new event entails changing the interface of the sender to allow for registration for the new event. If there are multiple senders of the same event, receivers must register interest with each of the senders individually.

Event Notifier centralizes the information that keeps track of who is interested in what. It also allows introduction of new event types at run time. Event Notifier also has a superclass for all events and a superclass for all subscribers.

Multicast does not support type-based event notification where one can subscribe for all events of a certain type (including subtypes).

While both patterns support extensibility in some sense, the crux of the issue is what can change and the impact the change has on existing code and existing deployed applications that use the pattern. For instance, if there is a new publisher for an existing event, subscribers for that event do not need to change if Event Notifier is used. In Multicast, receivers would all need to register with the new sender. Furthermore, in distributed environments where the redeployment cost of components is prohibitive but new event types are likely to be introduced, Multicast would require recompilation and redeployment. Event Notifier allows for this flexibility while preserving type safety when implemented in languages with rich run-time type information, such as Java.

Event Notifier provides this flexibility and insulates the application from the change since it does not require any recompilation by parts of the application that do not care about this change. There are no interface changes in the event service, subscriber, or publisher classes.


References

  1. Booch, G., I. Jacobson, and J. Rumbaugh. Unified Modeling Language User Guide, Addison-Wesley, Reading, MA, 1997.
  2. Gamma, E., R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1995.
  3. Gorlen, K., S. Orlow, and P. Plexico. Data Abstraction and Object-Oriented Programming in C++, John Wiley and Sons, 1990.
  4. Schmidt, D. and S. Vinoski. "The OMG Events Service," C++ Report, SIGS Publications, New York, NY, Volume 9 Number 2, Feb. 1997.
  5. Vlissides, J. "Pattern Hatching: Multicast," C++ Report, SIGS Publications, New York, NY, Volume 9 Number 8, Sep. 1997.


Author Biographies

Suchitra Gupta received his Ph.D. from the State University of New York at Stony Brook. He worked for the Pennsylvania State University at University Park as an Assistant Professor of Computer Science. He contributed to the design and development of reservoir simulators at Shell Oil Company. At present he works for U S WEST Communications. His recent work has focused on design and development of distributed applications using object-oriented techniques. He can be reached at bobbygupta@yahoo.com.

Jeffrey M. Hartkopf received his B.S. and M.S. in Computer Science from the University of Colorado at Boulder. He works for U S WEST Communications on a groundbreaking project in computing systems management. He has also participated in the development of an enterprise platform for large scale client-server applications. He can be reached at jeffhartkopf@yahoo.com.

Suresh Ramaswamy received his B.S. in Electrical Engineering from the University of Bombay and his M.S. in Electrical Engineering from the University of Southern California. He worked on CAD application development at Mentor Graphics Corporation. Presently, he works for U S WEST Communications on the use of distributed object technologies for building enterprise-class client-server applications. He also teaches introductory and advanced Java programming courses at the University of Colorado at Denver. He can be reached at suresh@dralasoft.com.