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.

4 thoughts on “Events Don’t Eliminate Dependencies”

  1. This is a very contrived example. Obviously in the case of a single consumer object, within the same JVM, you don’t need an event-driven design. You might as well start replacing every method call with passing of a message.

    This design would be useful if you need to add more listeners that process the message – say persisting it to a DB, sending it to a remote recovery host, adding more business logic in another thread, etc. What if you need to maintain a “legacy” consumer in parallel with a new implementation of the purchaseService? Then it becomes obvious what the benefits are.

  2. To me, the claim of potential “uncoupling” feature of a massage driven architecture is about the same as the scenario of a non Russian speaker that would randomly sign up for a class of quantum mechanics in Russia. As a consumer I need to know what and why I’m listening to something because there’s no room for questions and as a producer I need to make sure that everyone always understand me because I don’t really give a damn about them.
    The business case decides what dependencies you have. The only thing I think a message driven architecture is capable of decoupling is actively controlling flow. But that won’t solve your business case.

  3. logical coupling may remain, but you get rid of life-cycle and operational coupling. e.g. a purchase service my need frequents reconfiguration of suppliers channels, may need other activity based management, etc.

  4. I wouldn’t equate dependency and coupling. Or, at least, make a distinction between coupling at compile time and at runtime.

    I’ve found event/message driven systems useful whenever interactions between components that are created and destroyed frequently are rich.

    Dealing with constantly creating and destroying runtime links between objects is annoying, and leaves a lot of room for NPEs, regardless of language. Having a message bus in between, and having to deal only with letting the message bus know when an event consumer is born or killed yields code that’s much more sane – and also more robust, and easier to test.

    Now, whether that message bus is a full-featured, asynchronous, distributed, persistent, crash-safe and transactional message queue, or just a simple synchronous in-app proxy class, is secondary, and is a choice to be made on a case by case basis. But the heaviness of typical enterprisey MQ implementations isn’t a good reason not to use an event/message driven architecture.

Leave a Reply

Your email address will not be published. Required fields are marked *