Abstraction is a keystone principal of constructing software. Without the ability to isolate concerns we would not be able to build complex systems or run software on systems with differing hardware. Building abstraction into your software is generally a good and desirable thing that will make it easier to construct and maintain. It’s also something that is open to being overused.

There are many instances where an abstraction is used so that multiple implementations may be substituted. One particularly common use are device drivers which mediate between a common interface defined by an operating system and the custom requirements of a hardware device. Operating systems must be able to deal with many different variations of hardware. It is not feasible that an operating system integrates the code to talk to all devices, nor would such a design display adequate performance. This abstraction allows the construction of the drivers and the operating system to be separated in time and to be the responsibility of different groups, which are necessary features for this type of software.

Within the context of a single application this kind of need generally does not apply. The infrastructure pieces of an application will tend to remain constant over time. Once selected infrastructure components tend not be be swapped for alternatives (although they may be upgraded more frequently). However a great many applications include frameworks that abstract the infrastructure pieces. It is my contention that in most cases this adds little or no value.

My primary example of this is a logging system. I have seen (and myself written) framework code to provide my application with an abstraction of the logging system. Typically this results in an interface relatively similar to that of the underlying logging system (for instance log4net). The application code then uses the logging abstraction from the framework. It is therefore separated from the logging concern and the logging framework may be replaced without impacting the code.

This seems worthwhile, but it has a number of flaws. Firstly I am highly unlikely to replace my logging framework. Within a system these components tend to be stable over time, replaced only if there is a compelling need to do so. The abstraction in the framework is therefore providing my a capability I’m probably never going to need. It also abstracts the logging concern but logging frameworks such as log4net already provide a logging abstraction. This makes the logging support in the framework either duplicative or unnecessary while imposing a maintenance burden and introducing an extra level of complexity. This is not a good trade-off.

The same kind of concern applies to many other infrastructure pieces. The ability to change the database the application runs on is often listed as a benefit of data access technologies. This seems useful but I can count the number of systems I’ve worked on that needed this on no hands. ORMs are highly useful software components but it is highly unlikely that the primary benefit they will provide is the ability to switch your database. Applications that do need such functionality do exist but are in a decided minority.

There are of course exceptions, but these are limited and should be considered carefully. To return to the logging example some logging frameworks will not provide an abstraction, requiring you to work with the logging classes directly. This may require an abstraction but I would suggest it’s more a reason to select a different logging framework (assuming this is possible).

I will also provide a very thin abstraction for use within framework components that may be used in multiple projects. For instance a wrapper around a Dependency Injection container allows the framework to be used with projects that use varied DI implementations. In this case the abstraction would be used only within the framework and would be limited to supporting the needs of that framework. Projects using the framework should interact with the DI container they have chosen directly. This allows them to take advantage of the more advanced capabilities of the DI container without having to write a significant amount of entirely redundant wrapper code.