Architecture is about building a structure just strong enough to fulfill the requirement but not to much to keep the cost as low as possible. But in every project there is two kind of requirement: functional and non-functional. We think about functional requirement when they are explicitly express by the client.
I want a car that can take me from home to work and back.
The architect’s job is to make sure the car will be powerful enough to do the job, that it will be big enough to hold at least one person and to be able to carry enough fuel for the whole trip. If we stick just to those requirement we will end up with a single passenger car mostly made of plastic and with a gas tank of 1 L. Is this enough? That meets the initial requirements, but of course we have to take care of more than that. That’s were we have to think about non-functional requirements like security, standard, extensibility, maintainability, durability, usability and so on. Moreover, when a client ask for a car to go from home to work and back, he actually means he want to be able to go on vacation trip with it too, but you won’t know that at first.
To find a solution to all these requirements we have to do like we do when we face a complex problem; add abstraction layers. For a car that mean we have to respect some industry standard rules. For example we have make sure we can buy wheels, wipers, oil filters or cd player knowing it will fit on our car even if the combination of two specific model has never been tried before. As long as it respect standard like dimension it will work.
Is software development we have to set those standards for databases, components, UI interfaces, Logging, Error handling, and so on. We doo that usually with interfaces and abstract classes. Those abstraction will act as specifications anybody can use to build compatible pieces that will work on our system. If a car doesn’t expect to have more than 6 speakers it doesn’t make sense to build a cd player that can use 7 speakers. Likewise, is software, once we publish an interface, it doesn’t make sense to add other methods than the ones supported, they won’t be called anyway.
Now get back the initial question. How many layer does it take to abstract everything, and should we? If your are a purist you will try to add a layer of abstract between your code and all other code base you don’t have control over. But how far will you go? Is it a good thing to build your own control by deriving from the framework ones? Will you push the abstraction to the level of any part layer of your code can live independent of all others?
Remember what I said at the beginning? Just enough but not too much. So my answer is it depends but I like to leave me open doors so if later I want to split two layer it wont be too much difficult to do. Today with refactoring tools and test frameworks you can do big changes to your code without being too worry about breaking something.