I’m working up my chapter on the “Event Aggregator” pattern for my book this evening and I’m starting by collecting my thoughts on the subject. If you feel like you have to comment and give me free advice for this chapter, then I guess you should do that (please). I’ve written about the pattern before here and here. I’m also referencing some other patterns that I haven’t written much about yet. Ward Bell and John Papa have both blogged on these patterns. I’ll blog about my StoryTeller implementation tomorrow night.
In case you’re new to the pattern, the Event Aggregator object will:
Channel events from multiple objects into a single object to simplify registration for clients.
In essence, it’s a specialized form of the GoF Mediator pattern. It’s a “hub” object in your application that you can use to do decoupled “publish/subscribe” messaging between different parts of your application. I’ve used the pattern with WinForms apps, WPF with StoryTeller, and even JavaScript in web pages (going to be very important as we implement a dashboard pattern for our web app at work).
Here’s my braindump on the pattern:
- Registration. Somebody has to be responsible for registering the listeners with the event aggregator hub.
- You could have the listeners themselves do the registration by taking a dependency on the event aggregator as is idiomatic with Prism. This is great because it makes it obvious when looking at a class whether or not it is registered as a listener. I’m not a fan of this approach because I think it’s awkward and adds repetitive code to each listener – and repetitive code should be stamped out wherever it pops up.
- You could use another object like a “Screen Activator” (more on this pattern later) to do the registration of ViewModels/Presenters, screens, or non-UI elements as listeners. This has the advantage of removing the responsibility of bootstrapping away from the class (ViewModel/Presenter/service) that does the real work.
- This is really just 2a, but you could use a custom “Registry” class to make the explicit subscriber subscriptions in one place. I like
- Use conventional registration with an IoC tool to automatically add objects to the event aggregator as appropriate. I use a marker interface plus a StructureMap “interceptor” to do this in StoryTeller. This is the “easiest” mechanically, but adds some overhead to understanding how the pieces connect. But, and there’s a big but, conventions are black magic rather than explicit code.
- Discoverability/Traceability. An event aggregator is a form of indirection, and indirection almost always makes a system a little harder to understand. At some point you definitely need to understand what objects are publishing and which are receiving events. Strong typed events are a boon here because it’s relatively easy to use advanced IDE features like R#’s “Find Usages” to quickly determine publishers and subscribers to a particular type of event. The event aggregator analogue in CAB depended on string keys and made troubleshooting harder. Event aggregator implementations in JavaScript and other dynamic languages will have the same issue.
- Diagnostics. For those of us using static typed languages, you might want to add a diagnostic report that can be generated on demand that can scan the codebase and identify publishers and subscribers based on a dependency on the event aggregator. Using marker interfaces or common super types for message classes can make the diagnostics much easier.
- Event Filtering. Simply put, not every subscriber cares about every instance of a type of event. For example, StoryTeller has several widgets that need to respond to every single test event (queued, executing, finished), but the individual test screens only respond to events involving their one particular test. In this case you need to worry about how events are filtered. The responsibility for the filtering can be in:
- The listener itself. The listener knows what it cares about, so let it decide whether or not to continue processing the event.
- Filter within the EventAggregator itself. You can either register the listeners with a subject like this: EventAggregator.Register(this, myTest), but this is assuming a specialized event aggregator that “knows” about the subject. Another way is to make the registration with a Predicate or Func<T, bool> to filter within the event aggregator. I’m still experimenting here inside StoryTeller with this pattern a little bit.
- I’m thinking about having either an IoC interceptor or a Screen Activator do the filtered event registration. Again, the point is to move the grunt work of setting up the screen out of the ViewModel/Presenter to keep the ViewModel/Presenter relatively clean
- Thread Marshalling. It’s very handy to just let the event aggregator take care of marshalling callbacks to the screen back to the UI thread. The Prism Event Aggregator gives you fine grained control over whether or not the marshalling should take place. Maximum control might be nice when every bit of performance matters, but then again, it makes you error prone in a part of the code that is hard to test.
- Queuing. At this point I let the StoryTeller EventAggregator just process things synchronously, but running the event publishing on a background thread or queuing events up may be necessary to conserve resources.
- Open/Closed Principal. Using the Event Aggregator makes it much, much easier to add new features to your system without modifying existing code as you would have to if you were dependent upon direct communication without an event aggregator. This is an important issue for teams doing incremental delivery or cases where multiple teams are working on the same system in parallel.
- Garbage Collection: Your event aggregator has to keep a reference to all the subscribers. This can present you with some serious memory leak issues as screens are closed, but not garbage collected if the event aggregator is keeping a reference. You can beat the issue by using WeakReferences internally inside your event aggregator. The other option is to explicitly un-register listeners. The WeakReference strategy may be more reliable, but has its own issues. Explicit un-registration isn’t that bad if you are using a “Screen Conductor” to manage the screen activation lifecycle. Much more on that later…
- Event Latching. Two issues here:
- It might be valuable to ignore events at times. I’m specifically thinking about the case of a screen on a tab that is not active/displayed. Let’s say that this screen receives an event about financial market data being updated. The act of updating the hidden screen’s display turns out to be very expensive in terms of resources. You might want to quietly ignore or “latch” events when a screen is deactivated and hidden. That of course adds some complexity to make the hidden screen “know” to update itself when it is activated again. I think this is where the “Screen Activator” and “Screen Conductor” patterns come into play. If there’s a standard workflow that happens whenever a user activates a tab, then the “screen activator” should get an Activate() call.
- In some specialized cases you may want the Event Aggregator to “latch” itself while in the midst of responding to an event. This is especially important when a widget responding to an event publishes other events. Think about “change” events getting published during the act of binding a screen to new data. In this case the event aggregator should ignore new events until the first is completely finished.
- Event Ordering. It might be important that events be completely processed in the order that they arrive to the event aggregator. For example, Chad & I had an issue last year with a subscriber receiving an event, then publishing other events that were processed before the original event reached all of its subscribers. There might be a code smell in there somewhere, but event ordering may be something you need to consider.
- One size does not fit all: It can often be advantageous to have multiple event aggregators within one application. I often find it useful to use an event aggregator that is scoped within a single complex “Composite View” when a single screen is very complicated within its own right.
- Instrumentation. The EventAggregator is effectively a message bus and has all the same advantages as a message bus. Sending all events through the event aggregator gives you a great centralized place to put instrumentation code. Less repetitive code == fewer mistakes and better productivity.
What about the Prism EventAggregator?
Many people first come into contact with the Event Aggregator pattern through the implementation in Prism. For my regular readers you may be shocked (shocked I say!) to know that I don’t particularly care for the way they implemented the pattern in Prism. I think the Prism implementation is clumsy and awkward to use. I despise that idiom of first getting the event aggregator, then retrieving an “Event” object that I’ll then listen to. Same thing with publishing. I think it’s awkward that I have two steps (get event, then publish) instead of just saying “IEventAggregator.Send().” All of that is unnecessary noise code, and the “get event, then listen/publish” adds a little bit of overhead to every single unit test that I write that involves either listening to or sending events (more mock object setup, and that would add up more than the extra production code). No, that’s not a huge deal, but noise code adds up, and every bit of ceremony/noise code I can remove due to infrastructure will make me more productive and the code easier to deal with by making it easier to read.
All I want is to go:
- IEventAggregator.Send( the message ). Nothing else.
- The listeners should have little or preferably NO/ZILCH/NADA coupling to the event aggregator.
I think Prism is far better than CAB, but it’s still headed for some of the same problems that CAB had. The complexity and awkwardness of the EventAggregator in Prism is directly caused by trying to make the EventAggregator generalized to every possible scenario that you can think of. You will be able to create a better implementation of EventAggregator for your application by tailoring something simpler for only the things you need. At a minimum, you could at least put an application specific wrapper around Prism’s generalized API’s to make them easier to consume. I think you could stand to sacrifice some of the flexibility of the Prism EventAggregator and end up with a simpler to consume alternative.
Don’t take this as a specific criticism of Prism itself, because the real issue is that generalized application frameworks are an automatic compromise. The single most important reason that Prism is better than CAB is that you could easily, and I do mean easily, roll your own Event Aggregator and replace the one in Prism while still using the rest of Prism. Hooray for “Composition over Inheritance.” You couldn’t do that with CAB.
Wiki:
God willing and the river don’t rise, I will have a public Wiki up for the Presentation Patterns book by the end of the weekend. On advice from multiple people, I’ll be writing most of the first draft on the public Wiki. I’ll announce it as soon as it exists.
Posted
Tue, Jul 21 2009 10:22 PM
by
Jeremy D. Miller