| > there is no need to put a network in the middle to do the linker's job. Absolutely. More people need to understand the binding hierarchy: - "early binding": function A calls function B. A specific implementation is selected at compile+link time. - "late binding": function A calls function B. The available implementations are linked in at build time, but the specific implementation is selected at runtime. From this point on, code can select implementations that did not even exist at the time function A was written: - early binding + dynamic linking: a specific function name is selected at compile+link time, and the runtime linker picks an implementation in a fairly deterministic manner - late binding + dynamic linking: an implementation is selected at runtime in an extremely flexible way, but still within the same process - (D)COM/CORBA: an implementation of an object is found .. somewhere. This may be in a different thread, process, or system. The system provides transparent marshalling. - microservices: a function call involves marshalling an HTTP request to a piece of software potentially written by a different team and hosted in a datacenter somewhere on a different software lifecycle. At each stage, your ability to predict and control what happens at the time of making a function call goes down. Beyond in-process functions, your ability to get proper backtraces and breakpoint code is impaired. |
You see this under various guises in the web world - "event-driven", "microservices", "dependency injection", "module pattern". It's a very easy thing to see the appeal of, and seems to check a lot of "good architecture" boxes. There are a lot of upsides too - scaling, encapsulation, testability, modular updates.
Unfortunately, it also incurs a very high and non-obvious cost - that it's much more difficult to properly trace events through the system. Reasoning through any of these decoupled patterns frequently takes specialized constructs - additional debugging views, logging, or special instances with known state.
It is for this reason that I argue that lower-hierarchy bindings should be viewed with skepticism - if you _cannot_ manage to solve a problem with tight coupling, then resort to a looser coupling. Introduce a loose coupling when there is measurable downside to maintaining a tighter coupling. Even then, choose the next step down the heirarchy (i.e. a new file, class, or module rather than a new service or pubsub system).
Here, as everywhere, it is a tradeoff about how understandable versus flexible you build a system. I think it is very easy to lean towards flexibility to the detriment of progress.