Event Bus: Pros, Cons and Best Practices

Event buses are like lightsabers: properly trained, disciplined Jedis can use them to fight enemies and deflect blaster beams, but an average Joe would probably cut himself in two if he’d try to wield this powerful weapon.

In this post, I’ll try to summarize my experience with event buses in an attempt to bring you one step closer to become “event bus Jedi”.

Simple Event Bus Implementation

You can implement a very simple event bus like this:

public class EventBus {

    public interface Event {} // "marker" interface

    public interface Subscriber {
        public void onEvent(Event event);
    }

    private final Set<Subscriber> mSubscribers = new HashSet<>();

    public void publish(Event event) {
        for (Subscriber subscriber : mSubscribers) {
            subscriber.onEvent(event);
        }
    }

    public void subscribe(Subscriber subscriber) {
        mSubscribers.add(subscriber);
    }

    public void unsubscribe(Subscriber subscriber) {
        mSubscribers.remove(subscriber);
    }
}

This is a trivial implementation which wouldn’t be practical to use in most situations (e.g. it’s not thread-safe), but it does capture the essence of event buses. Design-wise, this class is almost a pure implementation of Observer design pattern.

At a higher level of abstraction, event buses realize so-called Publish-Subscribe architectural pattern (aka “pub-sub”). In this pattern, there is one group of clients that produce notifications (events), and another group of clients that subscribe to receive these notifications.

The main feature of pub-sub is that publishers and subscribers don’t need to know anything about each other, as long as they “agree” on the “type” of notifications being exchanged. Alternatively, I can say that pub-sub results in lack of direct coupling between publishers and subscribers which lowers the overall coupling in the system.

Defusing the Low Coupling Argument

In my experience, “low coupling” is universally seen as a positive characteristic. But is this indeed the case?

Theoretically, you could route all communication between components in your system through event bus, without direct method calls. Just replace every inter-component method call with a specific event and you’re done. The end result will be a system where components won’t have any direct inter-dependencies. Software architect’s paradise, isn’t it? [If you think that something like that can’t be implemented, then, I guess, you hadn’t been following micro-services hype season a couple of years ago. It was implemented, at scale, in multiple companies.]

I hope that even though I invoked the magical “low coupling” phrase, your intuition screams that something isn’t right about it and that it’s a bad idea. However, it’s not that simple to point out what’s wrong here exactly. That’s why “low coupling” often becomes developers’ trap.

Let’s defuse this trap.

For one, it’s not universally true that event buses lead to lower coupling. For example, let’s say that ComponentA called a method on ComponentB and you replace this method call with an event. In this scenario, you effectively replace ComponentA’s dependency on ComponentB with dependency on the event bus. In addition, you need to add the event bus as a dependency in ComponentB. Lastly, both ComponentA and ComponentB will need to depend on a specific type of event which will be used for communication. Therefore, the overall coupling inside the system might actually increase as a result of this refactoring.

However, even if you look at ComponentA exclusively, the situation isn’t necessarily better with event bus. Before the refactoring, ComponentA was coupled to a specific method in ComponentB. If you’d remove that method, your code would either fail to compile (statically typed language), or fail at runtime (dynamically typed language). In either case, you’d get some explicit error which would help you to debug the problem. However, after refactoring to pub-sub, you can remove the logic which handles that specific type of events from ComponentB (or just forget to add it in the first place) and that specific functionality would simply not work, silently. No fails, no errors, just frustrated users. This can be mitigated to some degree with automated testing, but even then it’s not that trivial.

That problem is a direct symptom of low coupling: the coupling between ComponentA and ComponentB isn’t strong enough to cause a failure if the integration between them breaks. In this situation, insufficient coupling becomes a drawback, not a benefit.

So, is low coupling bad then? Not at all. It’s only when the coupling becomes lower than optimal that it becomes a problem. And that’s really the main point of this section: software components need to be inter-coupled optimally, or, at least, close to that.

In some cases, the required level of inter-coupling between components should be very low. PCIe protocol comes to mind as an example. It’s also a pub-sub, but much more complex than event buses. Hardware components which connect to PCIe aren’t coupled to other components in the system, which allows you to use a vast variety of parts from different manufacturers inside your PC.

In other cases, like if you have two inter-dependent classes inside your codebase which implement a single feature, you want to have explicit direct coupling. Future maintainers of your codebase don’t need to trace events and reverse-engineer lifecycle overlaps to figure out communication scheme between two classes which reside in the same package. A simple method call is much better choice in this situation.

Event Bus Best Practices

I hope you enjoyed the theoretical discussion in the previous paragraph. However, to make an efficient use of event buses in your projects, it’s better to have a list of specific practical recommendations. Therefore, in this section I’ll share my personal list of best practices when working with event buses.

That’s it:

  • Default to not using event bus for communication
  • Consider using event bus when it’s difficult to couple the communicating components directly
  • Avoid having components which are both publishers and subscribers
  • Avoid “events chains” (i.e. flows that involve multiple sequential events)
  • Write tests to compensate for insufficient coupling and enforce inter-components integration
  • Put event classes into the respective “feature” packages

In addition, I also like a more explicit segregation between publishers and subscribers, so I don’t use event buses directly. Instead, I wrap them into this kind of proxies:

public class EventBusSubscriber {

    private final EventBus mEventBus;

    public EventBusSubscriber(EventBus eventBus) {
        mEventBus = eventBus;
    }

    public void subscribe(EventBus.Subscriber subscriber) {
        mEventBus.subscribe(subscriber);
    }

    public void unsubscribe(EventBus.Subscriber subscriber) {
        mEventBus.unsubscribe(subscriber);
    }
}

public class EventBusPublisher {

    private final EventBus mEventBus;

    public EventBusPublisher(EventBus eventBus) {
        mEventBus = eventBus;
    }


    public void publish(EventBus.Event event) {
        mEventBus.publish(event);
    }
}

The benefit of using these proxies instead of the actual event bus is that you’ll be able to infer whether a component is a publisher or a subscriber from its dependencies alone. It’s very beneficial to long-term maintainability.

Developers who took my SOLID course will surely immediately recognize this design trick as application of Interface Segregation Principle.

Event Bus in Android

The discussion up until now was very general and applies to any combination of programming languages and frameworks. But I also want to share some recommendations specifically for Android developers.

First of all, in Android projects, I recommend using EventBus library. I know that “you can implement it in RxJava in just couple of lines of code”, but, as much as I like DIY stuff, I don’t think it’s a good idea.

If you do use this library, then consider these best practices:

  • Use ThreadMode.MAIN_ORDERED in all subscribers by default.
  • Don’t ever use EventBus as a multithreading framework (i.e. for offloading work to background threads).
  • Sticky events can be very handy in some very specific situations, but realize that once you use them, you basically store part of app’s state in EventBus. In most cases, this trade-off isn’t justified.
  • You shouldn’t need EventBus’s annotation processor in Android apps. If you ever suspect that you do need it, treat it as a code smell and re-evaluate your design approach.

After you get your hands on this cool library, you’ll most probably see dozens of places where it can make your life much simpler. Many of these will be non-optimal and will lead to maintainability issues down the road. Be careful.

Stuff I routinely use EventBus for:

  • Sending user interaction notifications from DialogFragments to their “host” Activities and Fragments.
  • Sending notifications from Services to Activities and Fragments.
  • Non-domain communication between multiple on-screen controllers (e.g. synchronizing non-domain UI state between multiple Fragments)

The common denominator here is that it’s relatively cumbersome to implement direct coupling between these components due to the architecture of Android framework itself. Therefore, the solutions to these cases will resemble event buses to some degree. If that’s the case, then I prefer to use the real thing.

Conclusion

Event buses have been integral part of my toolbox for years and I use them in pretty much all projects. However, when I just found out about this seemingly simple and powerful approach, I abused it enormously. It took me about two years to comprehend the long-term effects of insufficient coupling and become respectful and mindful of event buses. I hope this article will allow you to spare all this time and trouble.

As usual, you can leave your comments and questions below.

If you liked this post, then you'll surely like my courses

7 thoughts on “Event Bus: Pros, Cons and Best Practices”

    • In theory, you can do that. However, you’ll need use some other means to ensure that the Service is “alive” because just subscribing to event bus won’t guarantee that.

      Reply
  1. Yes it makes sense. In general I always struggle on how to interact property with services in more architectural way. Should I somehow interact from viewmodel (or presenter in your case). Or from activity/fragment. I think that there are way too many code samples on how to load data from rest or db, with MVx, very few on interaction with service holding audio player or MQTT, web socket, bluetooth low energy connection or other more platform specific code. Hopefuly an idea for your next long post? 😊

    Reply
      • These days I mostly use bound services as something that will be alive while any activity of the app is in started state for foreground live communication that comes from socket connection for example but it is always mess to communicate with services.

        Thank you for all of the responses. Best regards.

        Reply
  2. Thank you for the post, Vasiliy. As I understand you consider event bus as the replacement for broadcast receivers?

    Reply
    • Dmitriy,
      You most probably mean LocalBroadcastReceiver. In this case, yes, event bus is much better alternative. As for general BroadcastReceiver, event buses can’t fully replace them becuase the former supports inter-process communication.

      Reply

Leave a Comment

Subscribe for new posts