Getting Hooked on Event-Driven Programming
Submitted by Matthew Turland on Wed, 01/21/2009 - 15:40A frequent topic of conversation that I see on places like Twitter concerns whether programming is an art or a science. Donald Knuth, a renowned academic in the field of computer science, is famous particularly for his 1974 Turing Award Lecture in which he says "Premature optimization is the root of all evil." He is also credited for another quote I rather like that relates to the science versus art discussion: "Science is knowledge which we understand so well that we can teach it to a computer; and if we don't fully understand something, it is an art to deal with it."
Despite being taught the Scientific Method as part of my primary (and fairly traditional) education, the assertions made in the textbooks I was obligated to read had a tone of finality to them. As such, I thought that science was something established, not something still being learned and discovered. Later experiences in college would change my stance; now, I see science as a set of perceived patterns within a particular knowledge domain like the patterns we see in software development.
Once such pattern is commonly referred to as a hook. It goes by other names, such as event handler and plugin. You might trace its origins to the event-driven programming paradigm and its associated event construct. If you've read The Pragmatic Programmer (specifically Chapter 5 Section 30 "Blackboards"), you might notice that it bears some resemblance to the concepts of tuple spaces and blackboards: events originate from publishers, go through a handler, and are consumed by subscribers before being removed. You might also conceive that it's related to the Observer or Delegate design patterns or the callback and signals and slots programming constructs.
If you're a moderately experienced PHP developer, you've probably had a few laps around the track with callbacks. You provide a specification for a function or method to be executed when a single predefined event occurs within another body of code, such as the PHP functions for array map and filter operations. Hooks go one level of abstraction beyond this and are intended for systems with multiple types of events and multiple potential listeners for each event type.
If you've used any of several popular version control systems, you might notice that both Subversion and Git have hooks for repository events that can be acted upon by multiple external executables. Event-driven systems are also the foundation upon which instant messaging protocols like XMPP are built.
And it doesn't stop with the software that we use; it's also in software we build and build upon. The Drupal content management system has a hooks system whose basic mode of operation is to check within each active module for a function with a name in the form "modulename_eventname" and, if that function exists, call it and pass in any data relevant to the event that has occurred.
Two traits of this system can be observed: 1) it favors convention over configuration (because the form of the hook function name is predetermined and fixed); and 2) each module only has one shot at intercepting events much in the same way that a class written in a single inheritance object-oriented language only has one shot at inheriting properties and methods from another class, which can lead to hook functions becoming rather messy if the developer needs multiple listeners for the same event.
For a different implementation, have a look at the CodeIgniter Framework and MediaWiki. These use more of a metaprogramming approach as described in Chapter 5 Section 27 "Metaprogramming" of The Pragmatic Programmer and Part IV Chapter 18 "Table-Driven Methods" of Code Complete.
I started looking at other implementations not tied to a particular project, or at least capable of being easily decoupled from an existing project. One implementation I came across was this one. First, it defines an interface for subscribers with a single method for intercepting events by name along with their parameters. Next, it declares a dispatcher class for handling subscriptions, which is done per class using a single instance per class created within the dispatcher, and receiving published events which are dispatched to subscribers.
It's at this point that I find the design starts to get a bit strange. The event class declared next is coupled to both the subscriber classes and the dispatcher class rather than simply being a "dumb" data structure. The only potential advantage I can see to this design is that subscriptions and publications are centralized in one class.
Another oddity is that the event dispatcher class is instantiated within the event class, which indicates that each event class would have its own dispatcher instance. As such, events are limited to the scope of subscribers defined by the event class. This necessitates changing the class for each event that a subscriber should receive. Whether you program in PHP or Java, this can't be good for cohesion. An alternative approach would be using one global dispatcher instance, namespacing event names, and having the subscriber class control what events it subscribes to so that it is the only class that requires changes.
Contrast this with another hook system. This one is comprised of a single class that allows any valid callback to be specified as a subscriber without requiring the implementation of a formal interface. It's very simple, drop-in, and completely opt-in. The class is available in both static and instance versions (if you prefer a dependency injection approach).
Because events can be namespaced and subscribers only receive the events to which they are subscribed, I'm skeptical of the amount of benefit gained from using DI to inject a single dispatcher instance into all components that use the hook system. The only potential application for having multiple dispatcher instances would be to separate events by concern.
However, this doesn't seem likely to yield a great performance advantage and decreases reversibility (as described in Chapter 2 Section 9 "Reversibility" of The Pragmatic Programmer) with respect to the ability to consolidate event dispatcher instances later on should routing requirements for events change. Conversely, the static class lends itself to static analysis for locating uses of the hook system in the event that they need to be modified or removed. Of course, the same could be said of API modifications that result from introducing the hook system into subscriber classes with a DI approach.
I tend to disagree with views that static methods are evil (completely anyway), though I have to admit that they can affect ease of testability for the worse. Static class references are final and increase the complexity required to override units of logic with mock objects so as to conduct unit tests on components in isolation from each other. Both approaches have advantages and disadvantages and hopefully this blog post had shed some light on them and their relation to the design of hook systems.


Re: Publish/Subscript systems
Um...
... I thought I did? See the last sentence of the third paragraph. ;)
It's me... by paragraph 12, I
Misconceptions on the first implementation reviewed
Hi Matthew, As the author of the first implementation ( Publish/Subscribe Implementation with the Zend Framework), I thought I might provide a bit of clarity on my design. The AppEvent.php class, in my example, was provided as a simple example of how one might associate events to subscribers and publish those events into the system. You would never define a class like this for each event in the system. In this example, it was intended to be a singleton for publishing events in which a single dispatcher would be defined. In the real world, you would most likely replace AppEvent class with one that would bootstrap event/subscriber definitions from a database or xml file. Alternatively, each subscriber class could inject their subscriptions directly into the dispatcher, as you suggested, if you did not need the flexibility of defining them at runtime. At any rate, I do appreciate you reviewing my implementation. Please let me know if this clears things up. - Kevin