Creating an independent data layer in your application gives you the flexibility of using multiple data stores as a data source and enables you to switch from one data store to the next quickly and easily. Designing an independent data layer is also a precursor to implementing a disconnected smart client application. This article looks at the benefits and drawbacks of building an independent data layer into your application.
What is an IDL?
When you design and build and independent data layer (IDL) application, you create a single set of components—an interface—from which your application retrieves data and to which it deposits data. If you want a list of customers, for example, you request it from the data layer. If you create a new customer record, you pass it to the data layer. If you update or delete a customer record, you send that request to the data layer. The data layer handles each request based on the data store it’s using.
The architecture of an IDL violates encapsulation, however, which is one of the primary tenants of object-oriented programming (OOP). A customer object should know how to search for, retrieve, validate, insert, update and delete data. With an IDL, you break out the responsibility for any database interaction into a separate object, and that fragments encapsulation to some extent.
You need to design your application to run on either custom business objects or strongly typed datasets. I prefer the custom business object approach. After all, we didn’t get fully object-oriented Visual Basic just to fall back on a construct that violates many common OOP tenants.
The design of the IDL is critical. For our purposes, we’ll explore one design, in which we will create:
- a set of interfaces
- an IDL shell with which the application will interact
- a set of classes that implements the actual data store interaction
First we should talk about class design. Each class should attempt to fully encapsulate the functionality for an entity. In our example, which we’ll call Customer, the customer class should have all the necessary properties. It should know how to validate customer data, for example, and it should be aware of the state of its own data. That is, it should know if the data has not been altered, is a new record, an altered existing record, or is scheduled for deletion. It should implement functions for insert, update and delete, or it should have a single function "save" that acts based on the state of the data.
Let’s create the IDL interfaces. Here is a sample of the ICustomer interface:
public CustomerCollection SearchCustomer(string
lastName, string firstName);
public Customer GetCustomer(int customerID);
public Customer SaveCustomer(Customer cust);
The SearchCustomer function allows the client to pass a customer’s first name or last name and returns a collection of customer objects. The GetCustomer function allows the client to request a specific customer by ID and returns a Customer object. The last function, SaveCustomer, accepts a customer object whose data needs saving. It processes the request and returns the customer object in its new state.
You should design an interface like this for each entity in your system.
Data store code implementation
Next you will build classes that implement the interfaces you defined. These classes will provide working versions of each method in the interface for the data store you are implementing. You repeat the process for each data store you intend to implement.
Using our customer example, we will implement the ICustomer interface for both a SQL Server and an XML data store. The SQL Server implementation of SearchCustomer creates a dynamic SQL statement based on the parameters passed. It will then open a connection to the database and execute the SQL statement. If results are returned, the function will load the resulting records into customer objects; the customer objects are placed into a collection and returned to the calling client.
The XML implementation of SearchCustomer constructs an xPath statement based on the parameters passed. An XMLDocument object is instantiated and the appropriate customer document is loaded. The results of the xPath Query are loaded into customer objects and those objects are placed into a collection and returned to the calling client.
IDL shell and switching mechanisms
The next step is to create IDL shell. This is a set of classes with which the application will interact. The classes act as a shell over the actual data store code implementations. Each interface must be implemented here as well, but the shell implementation of the interface is designed to call one of the data store code implementations to perform the function.
The complexity or simplicity of the IDL shell depends on how you switch the data store implementation you’re using. The simplest way is to use a config file. You create an application setting that indicates the data store to be used. A more complex switching system might enable the user to determine which data store to use or even have some other criteria-based engine determine the active data store. With a smart client application, the decision could be based on the presence of a SQL Server database on the network.
No matter what the switching mechanism, the client application should be able to call any function in the IDL shell. The function determines which data store implementation is being used. It then calls the same function on the data store implementation and returns the results to the client. The data store being used remains transparent to the client.
Benefits and drawbacks
While it can be a fun exercise to develop this type of architecture, it is time consuming. In fact, it takes more time and thought to implement an IDL than it takes to build an object-oriented class design with the data access built in. Unless you need the functionality, the basic OOP class design is the way to go.
An IDL makes sense, however, if you are building a smart client application, because you need to be able to run on either a local or network data store with equal ability. It is also ideal if you are designing software for large corporations and some potential customers use MS SQL Server, some use Sybase SQL Server, and others use Oracle.
I once had the opportunity to work on a complicated business application that was a combination of controls installed on the client, web-based client, and localized message queuing. It was often impossible to diagnose a problem without being on the workstation. With an IDL, I could walk into the production environment and easily toggle the application to the test database to evaluate the problem. This feature was also used for training classes.
An independent data layer solution is easier than ever to build in .NET, and there are many benefits to doing so. But if you really don’t need its functionality, stick with a traditional OOP approach. In the end it will be cleaner, simpler, faster and cheaper.