One of the known ways of decoupling on communication is the event driven software designs. Usually, using an external event/message-bus server is the way distributing the responsibility to internal/external services. But this also brings another external component to your stack. And each stack comes with its own problems.

P: Publisher, S: Subscriber, Line arrow: Publish, Dotted arrow: Subscribe

For small services, packages/libraries, and embedded systems adding an external bus could be unnecessary addition. To overcome this problem, adding an internal message-bus package to your package may help. And when things getting bigger, it is possible to communicate with this internal message-bus to external message-bus services.

Assume that, a project needs to do several things on user registration like; incrementing counters, sending email, logging, and/or sending webhook to external service; instead of calling each of these functionalities directly, a message-bus can help to execute multiple functionalities asynchronously without knowing the caller.