[ Team LiB ] Previous Section Next Section

Part 1: Decoupling Patterns

The previous chapter introduced the differences between application and middleware code. Developing application code requires extensive domain knowledge and experience with the business objects and processes that the software is supposed to model. On the other hand, middleware code consists of more technical details, for which programmers who are used to working with system-level resources and libraries are more qualified. Even small application development groups can benefit by dividing software into layers that separate application and middleware code. With a clean separation, application code deals exclusively with business objects and logic while middleware code handles system and database details.

Defining the exact nature of this separation requires you to consider the types of maintenance and enhancements that you expect to address in future releases. Ask these questions as you evaluate a potential architectural solution:

  • What features were dropped to meet schedules?— Even if you do not have time to implement these features, you should consider building isolated placeholders that facilitate their future incorporation.

  • What additional features do you expect your customers to request?— This is hard to predict and requires research and imagination. Consider meeting with potential customers for brainstorming sessions or usability testing. They can decide how to use your application in their environment and can help you determine what features are important.

  • Do you expect the underlying data model to change?— Will you be modifying the data model in future releases? Do you need to adapt to variations in the data model for different customer environments?

  • Do you expect to support new database platforms?— As you expand your customer base, you may be forced to support additional database platforms that partnerships and contracts dictate.

  • Do you plan to take advantage of advances in database technology?— If it is important for your application to utilize cutting-edge database technology, then consider which components need to adapt when something new comes along.

  • Are you dependent on third-party components?— When you find defects in your code, you have complete control of the debugging and repair process. However, if you identify a defect in a third-party component on which you depend, you have to wait for a fix from its vendor. In addition, it is not uncommon for professional partnerships to dissolve. In these cases, you may find yourself incorporating some competing technology in place of another vendor's component.

  • Do you expect performance problems to occur?— Many best practices recommend designing your software with a focus on structure and maintainability first, and fixing performance problems later. Even when you follow this good advice, you can still predict and isolate likely bottlenecks.

Decoupling patterns describe strategies for accommodating the issues that you raise when you answer these questions. The primary, common goal of these patterns is to decouple orthogonal software components. The extent to which you decouple components depends on how much you expect them to vary independently. For instance, if you expect to support additional database platforms in future releases, then it is a good idea to isolate all the code that is specific to a particular database in a separate, swappable component.

Another common example of decoupling is defining the line between applications and middleware. If you plan to expand your application or build new, similar applications, it is beneficial to decouple them from middleware code. This allows you to reuse the same middleware components and develop new applications more quickly.

The Data Model and Data Access

An application's data model is the static structure of its data. It encompasses one or more tables, any associated indices, views, and triggers, plus any referential integrity features defined between tables. The term "data model" also refers to an application's understanding of this static structure, whether it is hardwired or discovered dynamically using metadata.

In most cases, you define a data model with data that is intended to be used exclusively by your applications. From your customer's perspective, you encapsulate the specifics of the data model within your applications. This grants you leeway to change or add to the data model, since you can change the applications at the same time.

In some cases, you may publish your data model so that customers or consultants can integrate your applications' data into those from other vendors. A common example of this is a generic reporting tool that analyzes arbitrary data models and graphically summarizes their data. Another scenario is when a customer integrates your applications with others using an enterprise application integration (EAI) framework. If you support or encourage these scenarios, you are more limited to the types of changes that you can make to your applications' data models. For instance, you can add columns to a table, but you cannot remove columns without the risk of breaking dependent applications.

A final scenario is when your customers define their own data models. You can design your application to work explicitly with their legacy data. This degree of agility can be a significant selling feature, but your application must readily adapt to a variety of table configurations.

In contrast to its data model, an application's data access refers to its dynamic mechanism for reading and writing data. Data access code involves implementing direct database operations using commercial drivers or libraries.

You can choose to combine the notions of the data model and data access in your application. Combining them results in one or more cohesive database components that can take advantage of their data model knowledge to optimize data access operations. For example, this can allow a database component to form queries that explicitly take advantage of known table indices, an optimization that substantially improves query performance. On the other hand, separating an application's data model and data access enables you to change or more readily adapt to changes in the data model.

Domain Objects and Relational Data

A primary benefit of object-oriented programming is the ability to model your application domain directly in software. Writing application code that manipulates Customer and Account objects is more straightforward and less error-prone than computing offsets, passing large structures, and mapping all data to primitive types.

True domain objects model application concepts, not necessarily those imposed by the data model. This means that you should not always define objects based on the layout of tables and columns. Doing so binds your domain objects to the data model and forces applications to understand these details. Suppose you have CUSTOMERS and EMPLOYEES tables that, for historical reasons, include similar but not identical address columns. The column names and types may be inconsistent in the data model, but you can define Customer and Employee domain objects that expose the same Address object type. This consistency can lead to common address processing code that handles both customer and employee addresses identically.

Well-defined domain objects lead to cleaner application code, but present a problem for middleware. Object-oriented programming and relational databases are significantly different paradigms. Any software that utilizes both of these concepts must translate between them at some point. Mapping relational data to domain objects requires you to process query results and create analogous objects based on the application's data. Conversely, the other direction requires you to generate database operations that make changes to persistent relational data that corresponds to domain object changes. Figure P1.1 illustrates a mapping that translates between Customer, Employee, and Address domain objects and the corresponding relational data:

Figure P1.1. A domain object mapping translates between domain objects and relational data.

graphics/p01fig01.gif

Object-oriented databases that serialize and store objects directly in their run-time form provide one solution. They eliminate the need for extra domain object mapping altogether. However, object-oriented databases are not commonly used in enterprise software because they make it harder to integrate with other products that depend heavily on relational data models. In addition, most major database vendors do not provide object-oriented database engines.

Many applications therefore provide their own domain object mapping, intentionally or not. Even the most basic applications tend to tackle this problem on their own. In its simplest form, solving this problem requires an application to issue explicit database operations and manipulate corresponding domain objects with brute force.

This strategy gets unwieldy when many tables and objects are involved. As the magnitude of the mapping problems increases, you can address the problems with increasingly robust designs. One solution is to define a set of common operations or a framework that populates and persists domain objects in a uniform, consistent manner. Part 3, "Input/Output Patterns," describes some patterns that can help you build these types of structures.

You can also consider building or buying a full-fledged object/relational mapping tool. Object/relational mapping formalizes the process of mapping domain objects to and from relational data. Object/Relational Map (53) describes this strategy in detail.

Decoupling Patterns

Decoupling patterns define how application code relates to its data model and data access code. As you decide on an application architecture, you need to consider how much cohesion you want between orthogonal components based on how much you expect them to vary independently. Decoupling components also makes it easier to build and maintain them concurrently.

Another essential aspect of applying a decoupling pattern is defining the data access abstraction that it exposes to the rest of the system. This abstraction must be sufficiently versatile to expose the appropriate level of data access capabilities. On the other hand, it must also be broad enough to make it feasible to plug in alternate data sources and algorithms if required.

This part of the book contains chapters for each of the following patterns:

  • Data Accessor (9)— Encapsulates physical data access details in a single component, exposing only logical operations. Application code maintains knowledge about the underlying data model, but is decoupled from data access responsibilities.

  • Active Domain Object (33)— Encapsulates the data model and data access details within relevant domain object implementations. Active domain objects relieve application code of any direct database interaction.

  • Object/Relational Map (53)— Encapsulate the mapping between domain objects and relational data in a single component. An object/relational map decouples both application code and domain objects from the underlying data model and data access details.

  • Layers (75)— Stack orthogonal application features that address data access issues with increasing levels of abstraction.

These patterns differ in the level of database abstraction they expose to application code as well as the organization of data model awareness and data access within the architecture. These differences do not make them mutually exclusive. Layers (75) describes some examples of how you might combine these patterns to build a solution that completely separates an application's data model, data access, and domain object mapping. Extensive decoupling allows you to vary each of these components independently and leads to agile and adaptable software designs.

    [ Team LiB ] Previous Section Next Section