If we were to generalize the rule a bit and make it less legalistic, it might look something like this:
Limit what each module/class/function/etc knows about the rest of the system.
So you have two types of code:
- Code that glues our layers of abstraction together (App)
- The meat of the software that actually solves a problem (Book, Database)
Glue code exists solely so the meat code can do its work. Regardless what kind of code you're writing, you don't want your functions or modules to know too much about other areas of your software. This is for the sake of maintainability; the more pieces of code a module touches, the more likely you will have to change code in all those areas. Yuck.
Now when you're writing the "glue" code, you will obviously have to deal with modules that are spread widely throughout your software. But you're not actually using those modules. The glue code is actually quite dumb. It's calling some constructors and mostly just shuffling around references to objects. If your application gets large enough, your glue code will indeed get quite large and complex, but that's what the factory pattern is for.
So in my eyes, it's ok to traverse through multiple layers of abstraction in the glue code, so long as your glue code stays uninformed about how those layers are actually working together.
You should look into inversion of control containers. They are great little tools that allow you to really minimize how much glue code you have to write, thereby potentially increasing maintainability.