1

I am trying to find a good way of allowing two objects that are separated by a intermediate object to communicate while keeping the architecture loosely coupled. A solution I have developed is to pass method references to the two objects so that they can communicate with one another without needing to have an explicit reference to their shared "container" object. I am writing this code in Python, which support this behavior. I think this is a good solution, but I do not recognize the pattern and I am wondering if there are any potential issues with it that I am not aware of.

I am writing an IoT device simulator. The simulator is composed of multiple classes. The three important ones are the

  1. Instrument class
  2. MQTTMessenger class
  3. VirtualIoTDevice class

An instrument object constructs MQTTMessenger and VirtualDevice objects and keeps references to them as properties.

The MQTTMessenger subscribes to the topic associated with the IoT devices configuration on GCP IoT Core. A message is sent from IoT Core to the MQTTMessenger when the state changes.

Currently, when this message is received, the MQTTMessenger calls a on_message method, which uses a reference to the VirtualDevice's update_props method, and updates the properties of the device.

Similarly, when the device state properties are changed it sends a message to IoTCore by using a reference to the MQTTMessenger object's update_state method.

Another solution to this architecture would be to have both objects contain a reference to their containing Instrument and then call the methods directly, but I feel that this is a cleaner, less coupled solution. I have attached a diagram to illustrate the current structure.

Is this a good solution or are their pitfalls that I am not aware of? Is there a name for this design pattern?

Class Diagram

2 Answers 2

5

"I think this is a good solution, but I do not recognize the pattern"

Your diagram does not depict the pattern/structure you're asking about in a particularly explicit way, so I'll continue based on this:

"MQTTMessenger calls a on_message method, which uses a reference to the VirtualDevice's update_props method"

&

"when the device state properties are changed it [... makes use of...] a reference to the MQTTMessenger object's update_state method"

This is the same as having an interface that one class accepts (or maintains a reference to), and the other implements, it's just that you are constrained to a single method per interface. What corresponds to the interface here is the signature of the method. So this is, broadly speaking, just a way to do dependency inversion: two concretions - your VirtualDevice and MqttMessenger classes, rely on an abstraction - the (signature + meaning) of the method each of them references.

So, you have this

enter image description here

and this

enter image description here

The arrow with the white triangle indicates "implements", the other one is "references" (either as an internal field, or as a method parameter). When put together, you get:

enter image description here

The Instrument class then assembles these together; for the classes to remain decoupled, it is important that none of them depend on Instrument. You should be able to write an executable test for them without using or mentioning Instrument at all - the test code itself should be able to take over the role of a part assembler.

You may have seen a similar structure in some other contexts; e.g. Robert Martin's blogpost on Clean Architecture has a diagram with an an inset depicting an example of a dependency that crosses an architectural boundary:

enter image description here

This is almost exactly the same thing, except that the Presenter and the Controller are merged into a single object, and that in your case, the context is not the UI/Application boundary, but a communication layer / device layer boundary.

In this diagram, the layers are color-coded; note that the interfaces (boxes with an <I>, the ports) are "owned" by the Interactor (both of them are in the same layer as it), and that all dependencies (arrows) crossing the layer boundary (going from one color to another) are pointing in the same direction).

This arrangement determines which of the layers knows about the other layer, and sort of constrains how you put them together. Here, the Interactor and the associated interfaces are deemed more important than the presentation (UI) layer, so the UI depends on the application logic.

Now, this image might not exactly parallel your layer (or component) structure; in terms of where you draw your architectural boundary, you have two choices.

If the core/heart/soul of your application is about the devices themselves (if that's where the core logic lies), then you'd do this. Here, it's all about the devices, and the Messenger conforms to what they dictate. This favors being able to plug in different kinds of messengers (or other kinds of delivery mechanisms), assuming the update_state & update_props methods are robust enough for the needs of the device-side logic (meaning, you rarely need to change the signature of these methods when you need to plug in a new kind of messenger).

enter image description here

On the other hand, if core/heart/soul of your application is about the communication protocols between the devices, you'd probably place the boundary here:

enter image description here

This makes it all about the communication infrastructure, with the devices being able to plug into it by conforming to the interfaces (here, methods) required by it. This favors being able to plug in different kinds of devices, assuming the update_state & update_props methods are robust enough for the needs of the communication protocol (you rarely need to change the signature of these methods when you need to plug in a new kind of device).

So this has some implications on what these methods are actually going to look like, and how they'll evolve as your application matures.

Now, at this point, this boundary feels predominantly conceptual, but another way it comes into play is if you want to divide your application into separate packages, making the boundary physical. Currently, these interfaces (method signatures) are implemented by a method on one of your primary classes, but you could in principle provide a standalone function or even a specialized object (all variations on a similar theme, in the spirit of the Command Pattern Robert Harvey mentioned in a comment). Such a free function / closure / dedicated class would be placed into the same package as the "interface" if the "implements" arrow doesn't cross the boundary, and into the other package if it does.

Aside: In statically typed languages, one package (or DLL, or whatever) would actually statically reference the types in the other.

1
  • This is a very comprehensive answer and completely answered my question! Thank you!
    – dda2120
    Oct 31, 2021 at 21:52
2

The term you are looking for is event-driven programming. It is a technique that facilitates decoupling components, and is especially well-suited to asynchronous programming and concurrent computing. Events are a central concept in UI programming, and are the critical component to the MVC design pattern.

The major downfall with event-driven programming is also it's biggest asset. The separation of publisher and listener keeps components from requiring direct knowledge about one another. This also means the publisher can never really know who is listening. Debugging problems is a challenge due to this separation. Add asynchronous or concurrent programming on top of that and things get hairy pretty quick. Invest in good logging, especially when message queues are involved.

1

Your Answer

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

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