Events Don’t Eliminate Dependencies
Event (or message) driven systems (in their two flavors) have some benefits. I’ve already discussed why I think they are overused. But that’s not what I’m going to write about now.
I’m going to write (very briefly) about “dependencies” and “coupling”. It may seem that when we eliminate the compile-time dependencies, we eliminate coupling between components. For example:
class CustomerActions { void purchaseItem(int itemId) { //... purchaseService.makePurchase(item, userId); } }
vs
class CustomerActions { void purchaseItem(int itemId) { //... queue.sendMessage(new PurchaseItemMessage(item, userId)); } }
It looks as though your CustomerActions
class no longer depends on a PurchaseService
. It doesn’t care who will process the PurchaseItem
message. There will certainly be some PurchaseService
out there that will handle the message, but the former class is not tied to it at compile time. Which may look like a good example of “loose coupling”. But it isn’t.
First, the two classes may be loosely coupled in the first place. The fact that one interacts with the other doesn’t mean they are coupled – they are free to change independently, as long as the PurchaseService
maintains the contract of its makePurchase
method
Second, having eliminated the compile-time dependencies doesn’t mean we have eliminated logical dependencies. The event is sent, we need something to receive and process it. In many cases that’s a single target class, within the same VM/deployment. And the wikipedia article defines a way to measure coupling in terms of the data. Is it different in the two approaches above? No – in the first instance we will have to change the method definition, and in the second instance – the event class definition. And we will still have a processing class whose logic we may also have to change after changing the contract. In a way, the former class still depends logically on the latter class, even though that’s not explicitly realized at compile time.
The point is, the logical coupling remains. And by simply moving it into an event doesn’t give the “promised” benefits. In fact, it makes code harder to read and trace. While in the former case you’d simple ask your IDE for a call hierarchy, it may be harder to trace who produces and who consumes the given message. The event approach has some pluses – events may be pushed to a queue, but so can direct invocations (through a proxy, for example, as spring does with just a single @Async annotation).
Of course that’s a simplified use-case. More complicated ones would benefit from an event-driven approach, but in my view these use-cases rarely cover the whole application architecture; they are most often better suited for specific problems, e.g. the NIO library. And I’ll continue to perpetuate this common sense mantra – don’t do something unless you know what exactly are the benefits it gives you.
Reference: | Events Don’t Eliminate Dependencies from our JCG partner Bozhidar Bozhanov at the Bozho’s tech blog blog. |