Abstractions: You can have too much of a good thing

Architects love abstracting things. And why wouldn't they - it allows you to hide those pesky implementation details out of the way so they don't trouble your callers, and lets you completely change the implementation at a later stage, provided the interface isn't changed. But like most of the good things in life, there comes a point where more is no longer better.

All of the Enterprise Library application blocks include great examples of abstraction, generally through the use of the Factory and Plug-In patterns. An application using an Enterprise Library application block need only code against an interface or abstract base class, such as Database, AuthorizationProvider or Validator (or in some cases this is abstracted even further via a façade class such as Logger or ExceptionPolicy). The concrete implementation of each class is determined at runtime by a factory class, which will do things like inspect configuration files and attributes to figure out which one is appropriate. This pattern provides a lot of great benefits, as you can write code at an abstract level, such as "validate this!", "log this!" or "call this stored procedure!", while the specifics of how each of these is done can be encapsulated into a different class and changed without impacting your code.

Over the years I've spoken to a lot of people who wanted to (or actually did) take this a step further by completely abstracting away the use of application blocks by hiding them behind their own interfaces and factories. The main arguments I've heard for doing this are as follows:

  1. It provides an insurance policy just in case you ever need to get rid of Enterprise Library, by letting you build your own alternative implementation without requiring you to change your application code
  2. It lets you build reusable assets that can be used by different users who may or may not want to use Enterprise Library

I appreciate that these goals can be important for at least some people, however I'm far from convinced that abstracting away an Enterprise Library block is a good solution. David Hayden started a vibrant discussion on this topic around achieving this second goal in the Repository Factory, and continued it with a number of blog posts (here, here and here). Persistent though David is, I don't buy his arguments. This is not because I think the blocks are perfect and that nobody would ever want to use anything different. However since the blocks already expose an abstracted interface, any further abstractions would need to expose an almost identical interface (or potentially a much watered-down subset). David didn't share his example, but I'm assuming he built something like this for the DAAB:

public interface IDataAccessor{    int ExecuteNonQuery(string command, params object[] parameters);    IDataReader ExecuteReader(string command, params object[] parameters);    DataSet ExecuteDataSet(string command, params object[] parameters);    // Add as many more DAAB-like members as you want}public static class DataAccessorFactory{    public IDataAccessor Create(string instanceName)    {        // Look up configuration and figure out what to return    }}public class EnterpriseLibraryDataAccessor : IDataAccessor{    // Wraps an EntLib Database instance and defers interface calls to it}

This solution meets David's goals of breaking the dependency between the client code (such as the Repository Factory) and the Data Access Application Block. But let's think a little more about what we've achieved here. We've replaced the DAAB dependency with a dependency on a new interface which is almost identical to the DAAB. Any arguments around why the DAAB dependency may not be desirable should apply equally to this new interface (plus we've added more complexity and more moving parts without adding any new functionality).

Even putting this argument aside, another problem I have with this approach is that I can't really see anyone being able to build any other implementations of this interface that are different enough to be interesting. I'm not saying there aren't other interesting ways of accessing a database - solutions such as NHibernate and LINQ to SQL are great examples. But these are philosophically so different to DAAB that they couldn't possibly implement the same interface that was built primarily as a DAAB Abstractor.

The only way I can see to avoid this problem is to build an interface so abstract that it provides almost no functionality. Using Logging as as simpler example, one could build an interface like this:

public interface ILogger{    void Log(string message, string category)}

...and build different implementations that could talk to the Logging Application Block or Log4Net. But we were only able to do this because we dumbed down the interface to the lowest common denominator. Both logging solutions use quite different approaches for things like routing, filtering, formatting, and these decisions influenced the design of their native interfaces. In our desire to provide a single interface that is abstract enough to work with both solutions, we're going to lose a lot of functionality. I'll wager that not many developers are willing to give this up for the theoretical goodness that this additional layer of abstraction provides.

To finish off this discussion, I wanted to add support to Chris Tavares's observation that David's problem is really a design-time one, not a runtime one, meaning that once you've decided what classes you want to use to talk to your database you're unlikely to want to change your mind after deployment. While the abstraction patterns we've been talking about do allow for both design-time and runtime flexibility, Software Factories provide some additional options for design-time variability, such as code generation, that are probably more appropriate in this case. The nice thing about going this way is that at runtime you only have the code you need - and you have just the right amount of abstraction to provide a great flavour, and not so much to cause a stomach ache.