The point of the article is that callback-based APIs like this obscure the actual problems (like circular dependencies).
You're right that constrained designs prevents issues like these but callback-based APIs aren't inherently constrained. They allow anything to happen, which is why they conversely encourage errors like these (and are subject to pitfalls).
typedef enum {
CONSOLE_LOGGER,
DATABASE_LOGGER,
/* etc. */
} LoggerType;
void add_logger(LoggerType);
In this API you're constrained by what loggers you can add. It's not the totally unchecked free-for-all that a callback-based API provides. The user is strictly unable to shoot themselves in the foot.But this limits the expressive power that callbacks provide. Sorry I don't have any other API recommendations. The only way I can think of to stay expressive while safeguarding against unintended abuse is to include code analysis.
> fully resolving circular dependencies is equivalent to solving the halting problem, IIRC.
I don't think so. Any good dependency injection framework would detect this kind of circular dependency.
Just analyzing the dependency graph of injected components is indeed just a topological sort.
The message pumping example the author gives has the exact same issue. The core here is that two items (database & logger) are mutually dependant. That will always be an issue. It doesn't matter if your promises are circularly dependant, your context objects, your callbacks, or your globals.
Static analysis won't help - fully resolving circular dependencies is equivalent to solving the halting problem, IIRC.
And so the only way to prevent those dependencies is to constrain the design in a way that prevents circular dependencies. That's where the ad-hoc rules mentioned in the article stem from. They artificially restrain the problem space to prevent (classes of) circular dependencies.