Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. General talk
  3. Brainstorm
  4. Using a single shared Object as a message bus in the entire application?
Forum Updated to NodeBB v4.3 + New Features

Using a single shared Object as a message bus in the entire application?

Scheduled Pinned Locked Moved Unsolved Brainstorm
7 Posts 4 Posters 1.1k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T Offline
    T Offline
    thierryhenry14
    wrote on 14 Jun 2021, 19:19 last edited by thierryhenry14
    #1

    Qt's doc and examples seems to exclusively encourage putting signals in each component's class definitions. So that's what I did previously. However I recently found out that you can actually emit a signal in a different QObject. I always thought the "emit MySignal()" to be like a private call, but no, signals are public functions, you can do "someobj->MySignal()", or better yet "Q_EMIT someobj->MySignal()" to make it stand out more/make it greppable.

    I'd like to hear your thoughts on an application architecture style where you use a single shared object as a public message bus throughout the entire app. So all the components of the app have a reference to this QObject given in their constructor, all the "public" signals used by the application are defined in this QObject instead of being defined in the object that emits them.

    I've written a very simple example app to showcase this:
    https://gitlab.com/tarre/message-bus-test

    Notice how the features in the 2nd and 3rd commits were bolted on to the existing architecture in a very straight-forward way, without having to modify unrelated source files.

    Pros:

    1. Simpler application architecture. Since there's just a single source of major events, a new developer doesn't need to understand (as much) where their code fits with all the rest. They can add certain features by just writing signal handlers for signals coming from that single event bus. Also, they can discover how components interact with each other by searching for the usage of each signal from that single file. When writing new components, they don't need to think as much where to place it, since there's already this structure in place.

    2. Signals emitted by objects created at a low "depth" (for example Hardware -> SensorManager -> TemperatureSensor) can be used by components anywhere in the app in a simple clean way, without writing repetitive boilerplate code just to be able to connect a signal to a slot in a "far object". Also, this would have been something a new dev would struggle with.

    3. If there's a refactoring somewhere, if a different component is now responsible for emitting SignalX, consumers don't have to change anything. Previously the only way to do this would be to use interfaces, which is boilerplatey.

    Cons:

    1. Not Qt-ic (whatever the Qt equivalent of "pythonic" is)

    2. You lose the ability to use sender() to get a reference to the QObject* that did the real emit. With this, the sender is always the message bus object. (Correct me if I'm wrong. If there's a way to get the sender without adding it as an argument in every signal, that would be wonderful.)

    What do you think?

    K 1 Reply Last reply 15 Jun 2021, 13:38
    0
    • S Offline
      S Offline
      SGaist
      Lifetime Qt Champion
      wrote on 14 Jun 2021, 20:45 last edited by
      #2

      Hi,

      The signals are meant to be emitted from within the class they are defined in.
      They are there to tell interested parties that something has changed within the class.
      The fact that they are now public is an implementation detail.

      You also lose encapsulation with your implementation.

      You already have a message bus with for example DBus.

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      T 1 Reply Last reply 15 Jun 2021, 18:21
      4
      • V Offline
        V Offline
        VRonin
        wrote on 15 Jun 2021, 08:33 last edited by
        #3

        Pros 1: is a tooling problem, not an architecture one. GammaRay can help with that.

        Pros 2: Breaking of encapsulation is a major problem, you also lose a lot of abstraction. In your example, if you connect directly User->TemperatureSensor you lose the ability of changing implementations of TemperatureSensor and SensorManager without also changing User.

        Pros 3: it's a double edged sword. see point 2

        "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
        ~Napoleon Bonaparte

        On a crusade to banish setIndexWidget() from the holy land of Qt

        1 Reply Last reply
        3
        • T thierryhenry14
          14 Jun 2021, 19:19

          Qt's doc and examples seems to exclusively encourage putting signals in each component's class definitions. So that's what I did previously. However I recently found out that you can actually emit a signal in a different QObject. I always thought the "emit MySignal()" to be like a private call, but no, signals are public functions, you can do "someobj->MySignal()", or better yet "Q_EMIT someobj->MySignal()" to make it stand out more/make it greppable.

          I'd like to hear your thoughts on an application architecture style where you use a single shared object as a public message bus throughout the entire app. So all the components of the app have a reference to this QObject given in their constructor, all the "public" signals used by the application are defined in this QObject instead of being defined in the object that emits them.

          I've written a very simple example app to showcase this:
          https://gitlab.com/tarre/message-bus-test

          Notice how the features in the 2nd and 3rd commits were bolted on to the existing architecture in a very straight-forward way, without having to modify unrelated source files.

          Pros:

          1. Simpler application architecture. Since there's just a single source of major events, a new developer doesn't need to understand (as much) where their code fits with all the rest. They can add certain features by just writing signal handlers for signals coming from that single event bus. Also, they can discover how components interact with each other by searching for the usage of each signal from that single file. When writing new components, they don't need to think as much where to place it, since there's already this structure in place.

          2. Signals emitted by objects created at a low "depth" (for example Hardware -> SensorManager -> TemperatureSensor) can be used by components anywhere in the app in a simple clean way, without writing repetitive boilerplate code just to be able to connect a signal to a slot in a "far object". Also, this would have been something a new dev would struggle with.

          3. If there's a refactoring somewhere, if a different component is now responsible for emitting SignalX, consumers don't have to change anything. Previously the only way to do this would be to use interfaces, which is boilerplatey.

          Cons:

          1. Not Qt-ic (whatever the Qt equivalent of "pythonic" is)

          2. You lose the ability to use sender() to get a reference to the QObject* that did the real emit. With this, the sender is always the message bus object. (Correct me if I'm wrong. If there's a way to get the sender without adding it as an argument in every signal, that would be wonderful.)

          What do you think?

          K Offline
          K Offline
          kshegunov
          Moderators
          wrote on 15 Jun 2021, 13:38 last edited by kshegunov
          #4

          For reference, this was asked on the mailing list as well[1].

          As for the actual question, if you need a bus, then make a bus. Having one global QObject which aggregates all the signals for an application isn't a bus, it looks like one, but it's simply a singleton - an application global state. Additionally, you already have such a global object - QCoreApplication, you can send it (custom) events, which it will already ignore, and on the consumer side you can filter them out by installing an event filter and respond to what you want. However, the application object would be just your single point of contact, not one that'd be responsible for what you do with the events, nor should it care about what APIs the different components of your application expose. As @VRonin said, you're attempting to have the "god object", which is a bad idea - keep concerns separate instead.

          1. Simpler application architecture. Since there's just a single source of major events, a new developer doesn't need to understand (as much) where their code fits with all the rest.

          Quite the opposite, every object is going to be coupled to that global thing, so every developer will need to know where and when these signals originate and if it's valid to respond to them, and if responding to them won't create problems. Basically every developer is going to need to know everything about the system before they write a single line. That's what strong coupling does.

          They can add certain features by just writing signal handlers for signals coming from that single event bus.

          And emitting other signals may break stuff they have no idea about, since everything is coupled together.

          Also, they can discover how components interact with each other by searching for the usage of each signal from that single file.

          ... mainly by fixing hard to diagnose bugs in the object dependency chain ...

          1. Signals emitted by objects created at a low "depth" (for example Hardware -> SensorManager -> TemperatureSensor) can be used by components anywhere in the app in a simple clean way, without writing repetitive boilerplate code just to be able to connect a signal to a slot in a "far object". Also, this would have been something a new dev would struggle with.

          There's a better way - delegating the signals upwards. There's no boilerplate involved, you connect one signal to the next at the appropriate places and only if it makes sense. "Can be used by components anywhere" is exactly the opposite of how you design a flexible system. If they're components they should know as little as possible about other components, not the other way around.

          1. If there's a refactoring somewhere, if a different component is now responsible for emitting SignalX, consumers don't have to change anything. Previously the only way to do this would be to use interfaces, which is boilerplatey.

          You don't know that, because everything before the actual signal is hidden behind a wall. You can't tell if the same logic was kept before or after the signal(s) were emitted.

          1. Not Qt-ic (whatever the Qt equivalent of "pythonic" is)

          No such thing. It either is a good OO design, or it isn't. Code, style, personal preference comes later.

          1. You lose the ability to use sender() to get a reference to the QObject* that did the real emit. With this, the sender is always the message bus object. (Correct me if I'm wrong. If there's a way to get the sender without adding it as an argument in every signal, that would be wonderful.)

          You shouldn't use sender() to begin with due to all the listed reasons.

          In conclusion, if you want a bus, design yourself a bus - something like the DBus with a strict requirement on the data interfaces/messages and such and then roll with that.

          [1]: https://www.mail-archive.com/interest@qt-project.org/msg35958.html

          Read and abide by the Qt Code of Conduct

          1 Reply Last reply
          3
          • S SGaist
            14 Jun 2021, 20:45

            Hi,

            The signals are meant to be emitted from within the class they are defined in.
            They are there to tell interested parties that something has changed within the class.
            The fact that they are now public is an implementation detail.

            You also lose encapsulation with your implementation.

            You already have a message bus with for example DBus.

            T Offline
            T Offline
            thierryhenry14
            wrote on 15 Jun 2021, 18:21 last edited by
            #5
            This post is deleted!
            1 Reply Last reply
            0
            • T Offline
              T Offline
              thierryhenry14
              wrote on 15 Jun 2021, 18:46 last edited by
              #6

              Regarding breaking encapsulation...I don't see it as breaking encapsulation in spirit, because the appropriate emitter is still the one emitting the signal, it's just done in a bus QObject designed to facilitate connecting to signals. And the receiver has no idea who emitted the signal. Sure, the compiler won't prevent a RandomObject from emitting TemperatureChanged that's supposed to only be emitted by a TemperatureSensor, but just because the compiler won't stop you from doing something doesn't mean you're gonna do it, it would make no sense. The dev needs to assume responsability to a degree. Before Qt 5 we didn't even have compiler checks on connect() calls, didn't stop people from writing great Qt 4 apps.

              Regarding DBus...
              a) DBus is not crossplatform (there's mention of an "almost finished" Windows port on freedesktop, translation: unusable in production)
              b) I would need to constantly convert between C/C++ types and Qt types, write serialization/deserialization code, etc, which is a nightmare
              c) I would lose the automatic thread safety you get from Qt's AutoConnection (I know there's caveats, but for the most part it works great)
              This sounds horrible to me. I'm not seeing what I'm gaining by using "a real bus". A QObject is a real bus. A 50 line custom-written class is a real bus (and I'd do this before I use DBus). D-Bus can give me multi-process/IPC, but I don't need that.

              Regarding my QObject bus being singleton...what is your justification for saying this? The AppMessageBus in my example holds no state as you can see, it just has signals. Also, having the technical ability to use QCoreApplication doesn't mean I should do it. What does it gain me? Saving me from using a QObject designed purposefully for that task? That's a lot messier. Btw, if you look at my example, it's not a global variable, I give it as a constructor argument to the components that need it.

              K 1 Reply Last reply 20 Jun 2021, 21:24
              0
              • T thierryhenry14
                15 Jun 2021, 18:46

                Regarding breaking encapsulation...I don't see it as breaking encapsulation in spirit, because the appropriate emitter is still the one emitting the signal, it's just done in a bus QObject designed to facilitate connecting to signals. And the receiver has no idea who emitted the signal. Sure, the compiler won't prevent a RandomObject from emitting TemperatureChanged that's supposed to only be emitted by a TemperatureSensor, but just because the compiler won't stop you from doing something doesn't mean you're gonna do it, it would make no sense. The dev needs to assume responsability to a degree. Before Qt 5 we didn't even have compiler checks on connect() calls, didn't stop people from writing great Qt 4 apps.

                Regarding DBus...
                a) DBus is not crossplatform (there's mention of an "almost finished" Windows port on freedesktop, translation: unusable in production)
                b) I would need to constantly convert between C/C++ types and Qt types, write serialization/deserialization code, etc, which is a nightmare
                c) I would lose the automatic thread safety you get from Qt's AutoConnection (I know there's caveats, but for the most part it works great)
                This sounds horrible to me. I'm not seeing what I'm gaining by using "a real bus". A QObject is a real bus. A 50 line custom-written class is a real bus (and I'd do this before I use DBus). D-Bus can give me multi-process/IPC, but I don't need that.

                Regarding my QObject bus being singleton...what is your justification for saying this? The AppMessageBus in my example holds no state as you can see, it just has signals. Also, having the technical ability to use QCoreApplication doesn't mean I should do it. What does it gain me? Saving me from using a QObject designed purposefully for that task? That's a lot messier. Btw, if you look at my example, it's not a global variable, I give it as a constructor argument to the components that need it.

                K Offline
                K Offline
                kshegunov
                Moderators
                wrote on 20 Jun 2021, 21:24 last edited by kshegunov
                #7

                Sorry for the late reply, I was really busy the last few days.

                No need to get angry, you asked for an opinion, which is what I gave. Nobody said you must listen to my/our opinion.

                @thierryhenry14 said in Using a single shared Object as a message bus in the entire application?:

                Before Qt 5 we didn't even have compiler checks on connect() calls, didn't stop people from writing great Qt 4 apps.

                I guess, but on that particular note before Qt5 signals were private (for the mentioned reasons). The fact that you can now emit a signal from outside the object is an implementation detail. Don't get me wrong, I've done it, it's sometimes useful if you have coupled objects (like a manager class that may need to force the emission through its underling), but it still is frowned upon.

                Regarding DBus...

                The DBus mention was just as an example, no one implied you should use it for objects communication. It wasn't designed for this, on the contrary it was conceived as an IPC layer.

                b) I would need to constantly convert between C/C++ types and Qt types, write serialization/deserialization code, etc, which is a nightmare

                Actually Qt has a DBus implementation, but that's really moot.

                c) I would lose the automatic thread safety you get from Qt's AutoConnection (I know there's caveats, but for the most part it works great)

                Again, you could simply pump messages through the event loop. I don't see how using a signal-slot call is somehow better or different.

                This sounds horrible to me. I'm not seeing what I'm gaining by using "a real bus".

                You gain (some) decoupling of the implementation from the data interface. Or to phrase it another way:
                Why would unrelated components need to know what others implement, support, demand or represent. To give you an example, how does my radar control care about what data types are marshaled through and what interfaces are implemented by my data processing. Why would the radar component need to be recompiled (or possibly changed) to accommodate completely unrelated changes.

                Regarding my QObject bus being singleton...what is your justification for saying this?

                Just by merit of it coupling everything together. Everybody knows about it and it knows about everything.

                The AppMessageBus in my example holds no state as you can see, it just has signals.

                It doesn't hold its own state, it is a state for the whole application though. It basically does what QCoreApplication will have done - manage event dispatching. Btw, beside some initialization of unrelated (to this case) code, this is essentially the crux of the application object.

                Also, having the technical ability to use QCoreApplication doesn't mean I should do it.

                Of course, to reverse that argument though, having the technical ability to emit from outside a QObject doesn't necessarily mean that you should do it, right?

                Btw, if you look at my example, it's not a global variable, I give it as a constructor argument to the components that need it.

                So? That's simply an implementation detail. I create my singleton objects (whenever I need them) in main(), similarly to how QCoreApplication does it, but does that make them less global?

                Read and abide by the Qt Code of Conduct

                1 Reply Last reply
                2

                3/7

                15 Jun 2021, 08:33

                • Login

                • Login or register to search.
                3 out of 7
                • First post
                  3/7
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved