Context
The previous patterns in this section, Data Accessor (9), Active Domain Object (33), and Object/Relational Map (53), described strategies for decoupling the notions of the data model, domain object mapping, and data access details from application code. These notions are orthogonal because you can vary them independently of each other. For example, the Data Accessor pattern enables you to make data model changes without updating data access code. Likewise, if you employ an Object/Relational Map, you can change the domain object mapping implementation without altering application code.
Generally stated, software components are orthogonal if they address completely disjointed issues and can be assembled in any combination to build an overall solution. An application's data model is often orthogonal to its data access mechanism. The data model is the static structure of the data and data access addresses how to move the data between the physical database and the application code. Altering the data model, even drastically, does not usually require a change to the data access mechanism, and the converse is mostly true as well. Identifying orthogonal components in a system enables you to modularize it more effectively. Decoupling orthogonal components is nearly always a good idea because doing so grants you freedom to alter them independently.
Finer grained components can be orthogonal as well. Consider a data accessor instance that encapsulates all data access implementation code for a suite of applications. The code within this component addresses two distinct issues. The first is connection management, which involves initializing and resolving database connections. The other issue is generating SQL statements for database operations. There is very little overlap between the solutions for these issues so it makes sense to decouple them into separate modules.
As you think about orthogonal components, it is important to understand that orthogonality does not preclude dependence. In other words, just because two components solve completely different problems, it does not mean that you cannot build one of them using the other. For example, one way to decompose the data model and data access aspects of an application is to employ active domain objects that encapsulate their own mapping to the underlying data model. Rather than spreading data access code throughout active domain object implementations, you can utilize a common data accessor implementation within each. Active domain objects and data accessor implementation address orthogonal issues, and you can decouple them by defining a proper data accessor abstraction, but there remains a strong, dependent relationship of the active domain objects on this abstraction. Figure 4.1 illustrates this relationship as well as the decomposition of the connection management functionality from the rest of the data accessor implementation:

The Layers pattern describes how to stack multiple dependent, orthogonal components to form a robust and maintainable application. A layer is a set of components that implements a software abstraction in terms of less abstract entities. You can stack abstract layers that incrementally decompose a solution down to its ultimate physical implementation. Layers effectively manage the mapping from abstract concepts to a concrete implementation in steps, solving the problem one piece at a time. Figure 4.2 illustrates a stack of layers between application logic and physical database access in general terms:

As with any orthogonal components, it is a good practice to decouple layers so that they are only dependent on lower level abstractions rather than implementations. You can achieve this by defining abstractions for each layer. When you follow this strategy, you can view a single-layer implementation as an adapter from one abstraction to another. For example, the data accessor implementation in Figure 4.1 is an adapter that implements the data accessor abstraction in terms of the connection manager abstraction. Of course, it is not a pure adapter. Rather, it is likely to include a substantial amount of functionality as well. However, this single focus makes layered components easier to build and maintain.
Data access code is amenable to building with layers because there are so many orthogonal facets to it. Here are some examples of data access details that are candidates for implementation within their own layer:
Domain object mapping—
You can map between domain objects and relational concepts within a single layer. Active Domain Object (33), Object/Relational Map (53), and Domain Object Assembler (227) are domain object mapping patterns that you can confine to a single software layer.
Data conversion—
It is common for both databases and drivers to convert data between its physical and software formats slightly differently. This is especially true for timestamp data, BLOBs, complex objects, and character data stored using specific code pages. If you support multiple database platforms, you may find it useful to define a software layer that is devoted to normalizing tricky data conversion details.
Data operation mapping—
If you define logical database operations for your application to use like Data Accessor (9) suggests, then you can define the components that implement them in terms of physical operations within their own layer. The example given in this chapter takes this approach.
Resource management—
Database resource management is a good candidate for isolation within a single software layer because resource optimizations can have a significant effect on system performance and utilization. This includes strategies like connection pooling, statement caching, and resource timeouts. If you do not confine these operations within their own software layer, they may impose complex semantics on other components in your system. Defining one or more layers for resource management also makes it easier for you to start with a simple implementation and incorporate optimizations later.
Distribution—
If you plan to access data from multiple sources, it is beneficial to define a configurable directory rather than hardwiring data sources into your code. This allows you to readily adapt to new data source topologies. However, implementing a directory also adds complexity to an application's data access code, since every data access operation must target the appropriate data source. You can mitigate this complexity by defining a software layer whose inputs make no mention of physical data sources and outputs attach a data source to each operation.
Caching—
Caching data that your application accesses frequently can significantly improve its performance. Like other optimizations, caching introduces complexity, so it is yet another candidate for its own software layer. Caching layers typically define the same input and output operations, but intercept some input operations when cached data is available.
Authorization—
You can implement application-level authorization within a software layer by comparing operations to a set of authorization rules defined by an administrator. This usually involves checking rules for every data access operation, so it makes sense to confine authorization within its own software layer.
Logging—
Complete and consistent logging is extremely important when you debug defects that customers report. If you depend on application code to implement logging effectively, you run the risk of having to deal with missing or inaccurate diagnostic messages. You can implement logging within its own software layer, which intercepts and logs every data access operation consistently. You may even find it useful to define multiple logging layers that track operations as they pass through different abstraction levels.
 |