I'm looking to make a clean modular architecture. I hear all around how bad are circular dependencies, and I'm convinced that the less two modules are coupled, the most reusable the code will be.
But I want to handle edge cases where moduleA depends on moduleB and vice-versa.

Generally speaking, an architecture like this doesn't sound bad to me (very simplified):

enter image description here

All modules "register" themselves at startup into the bridge service. A module can have different interaction with all other modules:

  • ẁe can do bridgeService.hasModule('moduleName') to create "weak" dependencies
  • we can call a function of a module like bridgeService.moduleName.myFunction()
  • the bridge service can handle errors and virtualize the dependencies map of the application
  • It may happen that moduleA calls moduleB and moduleB => moduleA but it wont be a circular dependency except if functionA calls functionB and in return fnB calls fnA. In that particular case, javascript throw an explicit stack size exceed which is easy to fix.

I have a minimalist example that work well and I like the way it handles modularity.
The problem is I never found an example of such an architecture in literature, that's why I'm not so confident even if everything looks fine to me.

Is it a bad design ? What are the drawbacks or the positive about such a design ?

Thanks for your answers

NOTE: I'm working with nodeJs, thus I don't have to deal with compilation issues.

  • Keeping software modular is sometimes easier said than done. The intent here is to have the application take advantage of the module if it's present but still operate safely if it's not, correct? This approach looks like a reasonable approach to that. Jul 18 '20 at 0:55
  • Thank you, yess, that's right :) that also seemed right to me, but I can't find any literature about it and I'm a bit afraid to take a side path... Jul 18 '20 at 9:18
  • I know you will argue. :), but this issue is a sign that modules responsibilities are not correctly divided. Only place where modules can interact with others would be highest possible layer of your application - UI for example
    – Fabio
    Jul 18 '20 at 19:56

It looks to me you created a synchronous messaging system. If you make BridgeService a queue you will have something very common and proven, it will be asynchronous and the decoupling will be even better.

[Edit in response to comment]

Software components that communicate with each other through message queues is a common pattern, if you want to call it that. The queue can be an object in a single process (for in-process communication) or a database table on a separate server for inter-system communication, or something in between.

This is asynchronous by nature, the "caller" puts a message in the queue and the service will read and process the message when it gets around to do it. It may then respond by posting a message back, in a queue that is to be read by the caller. The caller can wait for that message, effectively making it synchronous again from its perspective, or rely on an event mechanism to get the results, if there are any.

All of the internet works that way because the IP protocol is a message protocol. Whether this model is a good fit for your application is for you to decide. It may not be the most convenient option, it requires a lot of plumbing which could be overkill.

  • Could you please explain further? Share links? I'm really looking forward to understand that, particularly the asynchronous part. Jul 18 '20 at 12:32

In short

It looks nice. But in reality, you just shift the problem, transforming inter-module dependencies in an interface segregation issue, that might ultimately end-up in object-oriented spagetthi-code.

More details

At first sight, it seems that you have reinvented some variant of the mediator pattern:

Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby reducing coupling.
- Wikipedia

The mediator is allowed to have a more specialised communication with the colleagues, using a more detailed interface than the colleague interface. So at first sight, your approach seems to implement a robust pattern.

However, the mediator aims at encapsulating how objects interact between them, and therefore hide unnecessary details about the colleagues to other colleagues. And here is the weakness of your variant: Your bridge exposes the full interface of each module. This is a real problem in view of interface segregation and dependency management:

  • Suppose you want to add a new function to module C: You'd need to provide this function also in the Bridge (change propagation) and inflate the bridge interface. But now all other modules mus now the new interface (ISP issue), and they might even use it via the brigde, even if they are not supposed to.
  • Suppose you want to change some parameters of a function in module C: you'll need to change the bidge interface (change propagation). But then you have to fin all the modules using the bridge, and see which one uses that function to change it as well (change propagation).

In the end, any change in any module might affect all the others. That's the exact contrary of decoupling. I'd call this object oriented spagetthi-code.

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.