Separation of Concerns
In practice, Separation of Concerns really just means congregating all code that pertains to a given task or task domain in the same place. A classic example is the task domain of database access. Consider this all-too-familiar scenario: One of my first tasks at a new job was to search through every single line of the company's Java code for mishandled database connections. The company's servers had to be rebooted every 48 hours or so when they ran out of connections.
|Warning Signs in SQL Code|
SQL statements defined "inline" in source code are a warning sign that the implementation or the project design itself probably includes some land mines.
If you see something like:
String sql = "SELECT CustomerID, LastName FROM Customers";
...you're most likely seeing code that is difficult to update or extend.
Had the developers centralized database access code in a single location, the code review would have been trivial. Instead, because they knew what data they needed for their class du jour, they just indiscriminately wrote code to go get it. In almost every case, this process included the following:
- Opening and closing the connection
- Creating queries with inline SQL
- Managing the returned database objects
The first two steps are downright tragic, and avoiding the last step completely could have created a considerable time benefit.
To demonstrate the benefits of Separation of Concerns and Loose Coupling in DAAB, this article examines a fairly typical middle-tier business object called "Product" (see Listing 1). Although greatly simplified for brevity, it nevertheless clearly illustrates the advantages of both the original DAAB code as well as improvements you can make to it. By virtue of factoring out database access to an isolated data access layer (SqlHelper.cs) , the
Product class is clean, simple, and quite resilient in the face of change.
As simple as it is, the
Product class nevertheless has a couple of interesting attributes. First, its simplicity is partly due to the fact that it has no database-access code at all. Normal database CRUD operations (create, update, and delete) are reduced to simple calls to
SqlHelper methods. Since
SqlHelper is a static method library, you don't even need an instance of the class to use it.
Product class is extremely resilient. Changes to the database-access layer will not require any changes to the
Product class. This was accomplished by doing the following:
- Using the
SqlHelper.SetSqlParameter method to create database command parameters. This method returns a database provider-independent reference to the parameter interface,
IDbDataParameter. No matter what specific parameter is being created on the other end, your business object dutifully passes it back to
SqlHelper when needed.
- Providing a delegate method that is passed an
IDataReader reference (in this case, the GetInstance and ProcessReader methods).
IDataReader is the interface that both
SqlDataReader must implement.
The delegate method, ProcessReader, contains only the logic that must be in the
Product class definition. That logic includes the columns that must be read using the
IDataReader reference. This is really the only thing that
SqlHelper cannot do on its own. Nevertheless, using an IDataReader reference instead of
SqlDataReader greatly reduces the level of coupling. This small feature allows the
Product class to be used by virtually any .NET-compatible data provider instead of just SQL Server (See Sidebar 1: Why Even True-blue Microsoft Shops Should Care About Loose Coupling).
Product class code should raise a couple questions, including, "Where's the base class,
BusinessBase, and why does
Product need it?" Listing 2 shows the code for
BusinessBase applies some business logic regarding the care and feeding of connection strings, why would you want every business object instance to maintain its own connection string? The simple answer is Loose Coupling. If business objects pick up their database connection information from keys in config files (a most common practice), then their implementations are tightly coupled to the database connection pools that the config file keys indicate.
This is usually not a problem, but in large, robust systems several database connection pools are often in use at once, each pool having different attributesand maybe even pointing to different servers. It would be efficient if any given instance of your
Product class could be used for any connection pool. Making the connection string a property of the base class enables that.