From time to time I’ve encountered scenarios where several complex conditions need to be met prior to triggering an event. Furthermore, most listeners also run additional checks to determine the course of action. This got me thinking whether a better solution would be to think in terms of smaller events and let them trigger inside each other.
Chaining events would allow me to weave in any additional listeners later on with fairly low effort (possible violation of YAGNI?). My code would consist of simple easily understood elements, which shouldn’t be difficult for others to understand.
However, the possible downsides to this solution would be the fact that should something happen to go wrong in the chain (e.g false event triggering due to human error), it would be quite difficult to catch the bug.
Is event chaining a good ideaTM? If not, what are the alternative methods to keep event related code getting cluttered?
1
Is event chaining a good idea?
It’s one of those things that seems like a really good idea, until you use it.
It is very difficult to set up cascading events without some sort of implied dependency on order. It’s difficult to set them up without causing issues due to infinite loops and occasional memory leaks. They make class design more difficult due to coupling caused by events needing to know both where to attach and where to cascade to.
And they’re super hard on debugging and reasoning about code.
Now sometimes they can be used in relatively limited scenarios where the structure of the code limits some of these problems. In UIs, cascading events can be used to trigger down the hierarchy because that hierarchy structure helps limit ownership and looping concerns.
Still, I find far more often these days that I accept a delegate in a constructor for that sort of extensible behavior than let arbitrary behavior latch on at runtime.
1
Event chaining is a good idea if
- It’s generally appropriate to your scenario. A simple example is a user’s UI action triggering other visual events.
- Each event is self-contained and manageable. You don’t want the solution to become overly cumbersome.
- The flow of control is easy to follow. It needs to be implemented on a platform and in a language that’s easy for a developer to walk through. If you need to track down “magic” methods to trace what’s happening, you’re going down the wrong path.
It’s very important to think the solution through and generalize some things before starting to build the system. For example, in an OO language you should have a basic interface or abstract class as the basis for all events. That class should incorporate things like logging / debugging. You might also want a generalized event management class for handling failures gracefully.
Speaking from the standpoint of someone who once spent a couple of days tracking down an event-chain-related error, this is a Very Bad Idea(sm). You’re hiding your control flow which (as you noted) can make debugging a nightmare. The situation I was in arose when someone added some error-handling code which reset a control. This led to a chain of onPropertyChange
handlers that wound up refreshing the control which had the error handler, which led to it resetting the other control again, and so on. Basically the UI would simply lock up with the CPU pegged at 100%.
If you have some way to prevent event handlers from being triggered more than once for the same root event, then you might be able to avoid this, but I can imagine situations where you might want multiple event handler invocations.
2
Implementing event chaining well is difficult, for all the reasons mentioned by others.
However, it is also the basic premise of most rules engines. JBoss Drools, IBM jRules, PegaSystems, Corticon, and FICO Blaze Advisor are all major Business Rules Management Systems (BRMS) that allow users to declare rules that fire off based on events that occur in the systems. Both forward- and backward-chaining are possible and doable.
The Prolog language and its derivatives are based on the same notion.
The algorithms involved are not simple, debugging CAN be a pain, but there is a lot of value to be found in the model.
One potential drawback is that it’s quite easy to accidentally end up with looping updates. e.g. A -> B -> C -> A -> B…
Another approach is to create composite events which are responsible for fire off a sequence of events. This means you shouldn’t end up stuck in a loop, and gives you a single place to catch errors/etc. I’ve had some success with this, though admittedly haven’t used it for anything particularly complicated (yet!).