0

I've been using the publish-subscribe pattern for a while for communicating devices and it works really well, making it easy to add extra services without touching the rest. I'm thinking of using it inside an application (think of a monolith or a GUI application).

The basic idea is: every component can subscribe to any event, and can publish events. Every component will keep its own view of the state it needs.

Two additional benefits for this pattern when used on a standalone application:

  • I can get a compiler to throw errors when new events are added (using enums etc), making it easier to review whether existent components should be updated when a new event is added.
  • Individual components can't crash and miss data (the whole app would crash) making error handling easier too.

What are some examples of this being used (or abandoned)? I have never seen this in apps or libraries. The observer and mediator patterns are more typical.

0

2 Answers 2

2

Publish/Subscribe does exist in applications. At the lower levels, desktop applications follow a pattern of receiving input, invoking handlers for that input, and updating the screen. All major GUI software operates in this manner. However, it's not typically suited for higher level abstractions such as business level data that is updated on a timer or due to network traffic.

For higher level abstractions, Publish/Subscribe is called an "Event Bus" (and also here). I've used it in a few desktop applications where you need to update different sections of the UI when you are receiving changes in a very localized bit of code.

The challenge you have with asynchronous operations in a desktop application is when the screen needs to redraw due to those changes. So the Event Bus has two different responsibilities:

  • Notifying the appropriate handlers when an event happens
  • Invoking the event in the right thread context

The Publish side of an event bus based desktop application would have a direct reference to the event bus:

eventBus.publish(dataEvent);

On the Subscribe side of an event bus, the receiving code has to register for the events it cares about:

eventBus.subscribe<MyDataEvent>(this.handleMyDataEvent);

and then implement the handler itself:

public void handleMyEvent(MyDataEvent dataEvent) {
    // ... do something with the event
}

Lastly, to prevent memory leaks, you want to ensure that any client that listens for events unregisters their handler when that code is cleaned up:

destructor() {
    eventBus.unsubscribe(this.handleMyEvent);
}

Of course, the Event Bus approach isn't perfect, and there was a period of time where it was overused. That said, it can work well for applications that need to update based on events that are pushed to the app from network traffic, timers, or any other asynchronous mechanism.

0

Berin gave already a good answer, and I can confirm I have implemented such an "event bus" inside a larger desktop application by myself. What I would like to add here is that it is not just useful for async operations, but also for synchronous ones.

Whenever you have to deal with different views in a UI to the same data, where the views can be opened and closed by the user at will, and also multiple dialogs or operations which can manipulate the data, using an event bus starts to make sense. Manipulating components rise "data changed" events, and view components subscribe to them, or unsubscribe when getting closed.

Even when the dialogs, views and the event bus as well are running all in the GUI thread, this helps to keep the different components decoupled, so they don't need any direct dependencies at compile time, and can have independent life times at run time.

On the other hand, I don't see the two points you mentioned as benefits (or maybe I misunderstood them)?

I can get a compiler to throw errors when new events are added (using enums etc), making it easier to review whether existent components should be updated when a new event is added.

One does not get a compiler error when an enum is extended or a new event is added. When there are 10 events available, and a component subscribes to two, there is no obligation to subscribe to the others, or unsubscribe to the other 8 explicitly, this does not make much sense. And when event #11 is added to the list of available ones, you have to decide deliberately which components now have to subscribe to the new one, the compiler won't tell you.

Individual components can't crash and miss data (the whole app would crash) making error handling easier too.

When you have an application made of several components running in the same thread, and one of the crashes, that will likely crash the whole process, that is correct. However, this has nothing to do with the usage of an event bus and would be equally true when your components know each other and communicate directly with each other.

4
  • "One does not get a compiler error when an enum is extended or a new event is added." Some compilers on some languages will warn or throw an error if you don't handle every case for an enum (For example Rust, but C/C++ too). As long as you don't use "default" on your event handler, you can take advantage of that.
    – Josu Goñi
    Aug 5 at 12:13
  • "However, this has nothing to do with the usage of an event bus and would be equally true when your components know each other and communicate directly with each other." -> The advantage is compared to an event bus running over network (microservices etc): some components might not be listening all the time, and that adds additional complexity.
    – Josu Goñi
    Aug 5 at 12:15
  • @JosuGoñi: "if you don't handle every case for an enum" - sounds like you have a very special kind of event handling in mind, something which incorporates a huge switch statement. I can only guess what you envisioning here, but I can assure you, by last event bus implemenation looked differently.
    – Doc Brown
    Aug 5 at 13:04
  • and "The advantage is compared to an event bus running over network" - sorry, but what are we comparing here? I assume you have a monolithic application as given and we compare the benefits of using publisher/subscriber vs. implementing the communication between components directly. Not "we need to use publisher/subscriber and now we compare "Monollithic app" vs. Microservices".
    – Doc Brown
    Aug 5 at 13:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.