Change breaks things. Hiding change limits how far that breaking goes and makes changing easier.
If you want to look at this in the most abstract way: this is simply indirection.
A famous aphorism of Butler Lampson goes: “All problems in computer science can be solved by another level of indirection” (the “fundamental theorem of software engineering”). This is often deliberately mis-quoted with “abstraction layer” substituted for “level of indirection”. An often cited corollary to this is, “…except for the problem of too many layers of indirection.”
wikipedia.org – Indirection
Which is simply a way to say, sure you can do this. But it aint free. It comes with a cost.
When you say:
Here is the part I don’t understand. I just don’t see an API reducing all those dependencies, for me an API just provides endpoints to be called from outside the application… How come that all that coupling is going to automatically dissapear if I change my backend to be connected within an API?
You are 100% right. Not a single dependency has been eliminated. The users have exactly the same needs that they had before.
What’s different now is the expression of those needs (what you’d look at to learn what they are) is no longer mixed together with service implementation details and scattered over many desperate services. Separating the implementation and the interface isn’t simply to make testing or polymorphism easier. It also makes reading code easier. A reviewer of your code now has a tidy place to go to learn exactly what user needs you’re offering to satisfy.
And from the user perspective there is less coupling simply because they don’t know what is satisfying their needs.
I’m just too blind to see how it would work… I’d love to understand it because I’ve been using TDD for two years now and I have suffered that pain he talks about whenever I need to refactor my tests just because we perform a clean up in the backend.
TDD causes early pain. Technical debt causes late pain. So long as the early pain is cheaper than the late pain you got a bargain.
But that doesn’t mean there isn’t some learning to do. TDD can be done in very messed up ways. From locking down things to where change is actually harder with TDD (test against interfaces not implementation details to avoid most of this) to testing requirements that either don’t exist anymore or never should have existed in the first place (map tests to requirements if names don’t make it obvious).
However, just because you have to refactor tests doesn’t mean you did something wrong. Sometimes it means a requirement changed. Sometimes it means you’ve restructured the code. TDD never promised that tests never change.
In fact, there is a rigorous methodology for refactoring tests. Michael Feathers gave us refactoring against the red bar. Apparently he got it from Elizabeth Keogh. I’ve talked about it before. It walks you through steps to change a unit test so that the code under test can’t sneak away from you.
Which is nice, but again, it isn’t free. Change comes at a cost.