3

I'm working on a multi-threaded program that interfaces with external USB/serial devices via user-space device drivers.

Early in the design stage, I made the decision to split the program into three components: A, B and C.

  • Component A would possess full responsibility of communication with external devices (this is where the user-space device drivers would run). It would run on a dedicated thread.
  • Component B would serve an API off of a TCP/IP socket to third-party clients that needed access to the external devices. This component would also run on a dedicated thread.
  • Component C would provide a GUI for the user, allowing them to view and manipulate data from the external devices. Again, this would be on a dedicated thread.

So components B and C both require access to the external devices, which component A would provide.

I needed a way for the three components to interface with each other, and at the time I thought making the program event-driven would be appropriate. With this approach, components would emit an event and other components would handle those events. I always knew that some events would require responses, in the form of subsequent events. For example, if component C wanted to pull some data from the external device, to present to the user in the GUI, it would emit an event to request the data from component A, and component A would handle that event, and return the requested data in a subsequent event, which component C would be waiting for.

Since defining the above approach, I've realised that, in some cases, a component may require a specific response to a specific event. So building off of the example above, if component C and component B requested different data from component A, component C should be able to wait for the correct response event (that is, the response to the event emitted by component C).

So I'm considering implementing the ability for a component to wait for an event that is a direct response to the previously emitted event. I would do this via event IDs, where each event would carry an ID, and an optional response ID. The response ID would be the ID of the event that the current event is in response to.

But I feel like I'm on the wrong path. Is this really an appropriate use of event-driven design? Is it OK for events to serve as requests for data, and subsequent events as responses? Would you do it differently? If so, how?

EDIT/UPDATE:

I've just come across this video by Mark Richards, which seems to describe my approach with event IDs and response IDs (which he calls "correlation IDs"). So maybe I'm not on the wrong path - he seems to think using events for request/response is fine. Would still appreciate your thoughts.

0

1 Answer 1

2

Event is a very broad term. It only means that something has happened. And event driven only means that something happens as a direct reaction to a monitored event.

What event driven does not define is how events are monitored or how a communication API is designed that tells you about events; thus it also does not define, if event notifcations are "one to one" or "one to many" communication and, in the first case, also not if they are "unidirectional" or "bidirectional".

E.g. if an event notification is only sent to one listener at a time, it may contain a reply callback (callback function, callback block/lambda, callback object, etc.) that the receiver can call/perform/schedule once it processed the event and want to pass back a reply to the entity that triggered the event notification.

The Linux D-BUS system knows two different kind of "events":

  • One-to-one request-response: A sender only sends events to exactly one listener (request) and this listener can send back a reply to the request received.
  • Publish/subscribe: Any number listeners can subscribe to these and the event is published, yet these events cannot have a reply.

You are suggesting to only support the second system and misuse it to emulate the first one. Yet this only steers up issues:

What if two "entities" listen to the same event and both send an answer back using a new notification? What will happen then? Only one of them will be processed? Which one? Both will be processed? And what if they contradict? I only see lots of trouble here and absolutely no benefit.

Cannot happen since at any time there will be only one listener that replies? Then why the loose coupling to begin with, why not a tight coupling using a client-server or delegate pattern?

It's still an event driven approach if the listener has to first register somewhere and then will be the only one receiving the event as a request with the possibility to directly reply to it, it's just one that uses tight coupling which always makes sense if a reply is required and responsibilities are clearly defined. Loose coupling never makes sense if a reply is expected, it only makes sense if many listeners just shall be made aware of something and may or may not react to it and nobody has to react to it.

Also one does not exclude the other one. The same event might once be sent to a delegate/client/worker using one-to-one communication and thus allowing a direct reply while at the same time being broadcasted as one-to-many notification for all other components whose task is not to control anything yet they may still want to know that the event took place.

3
  • Thanks for your answer, Mecki. In this case the "Cannot happen since at any time there will be only one listener that replies" applies here, but the reason I still need the loose coupling is because other components may be interested in the reply from the listener. So say if component B requests data from component A, component C may be interested in what data was returned by component A. It's just component B needs to ensure that it's waiting for the correct response event from component A. Apologies if that doesn't make sense! Commented Jan 17, 2021 at 15:41
  • So it's pretty much what you described in your last paragraph. Commented Jan 17, 2021 at 15:44
  • 1
    @JohnO'brien other components may be interested in the reply from the listener -- In that case I would still use tight coupling for the request/reply and additionally broadcast both as event for those other components, that can not actively take part in processing but will know that a request was sent and how someone reacted to it. This keeps responsibilities clean (the code leaves no doubt who will actively handle a request), yet everyone can react to the event on its own as well.
    – Mecki
    Commented Jan 17, 2021 at 15:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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