At this point, you have a system with a lot of depth in its features. However, you still have a very narrow scope, since all the data services only support three methods. You could argue that these three methods support a very wide range of data operations, but I would expect these services to be more feature-rich. Here are some ideas of what you might add to your data services:
- Add transaction support to the IDataService interface (and all implementations thereof) through methods such as BeginTransaction() and CommitTransaction().
- Add code that attempts to detect SQL injection attacks by looking for comparison operators in the command text and see if they all use the parameters collection, rather than parameter values stored in the command string. The current architecture already provides some protection against SQL injection by supporting parameters, but this feature forces people to take the safe route.
- Add security features such as application role support.
- Add support for data readers. Data readers allow very fast and resource-friendly reading of data. The difficulty in the above scenario is that readers do not support the distributed scenarios as well as DataSets do. However, it can still be done with specialized reader handling in distributed environments. Performance will suffer a lot in those cases, but on the LAN, readers provide great performance, and over the Internet, users should not expect the same performance anyway, right?
- Add methods that can load data asynchronously in a thread-safe fashion.
- Add concurrency checking to the data service and raise events whenever concurrency problems occur, so developers can write interfaces that react to those events.
These are very technical features, but you may also be interested in adding more task-oriented features. Here are some ideas for those:
- Create methods that can create command objects that perform CRUD operations (insert, update, delete) for updated rows in data tables. The command builder object can perform this task too, but you can provide more sophisticated or more specialized functionality.
- Create methods that can create command objects for standard operations, such as retrieving a row based on the key value, retrieving a list of records based on foreign key values or the value of any given single field.
If you can abstract many of the standard data access operations in this fashion and put the burden on each data service object rather than your business logic, you can write very generic data access code that works in an optimized fashion on any given data source. Depending on your application, these methods can handle a very large percentage of data access operations, making it much easier to build applications that support various database technologies.
Even more polish could be added on the business object level. Currently, any code using this infrastructure needs to instantiate a data service. In a business object, you can add one more layer of abstraction by adding a protected ExecuteQuery()
method to your base business object class, which is then available on all concrete business objects. This method is a direct map to the data service's ExecuteQuery()
method, except that it also takes care of instantiating the data service, taking that burden off developers. The same can be done with all other methods exposed by the data service.
|The architecture you created in this article is really just the beginning....|
As you can see, the architecture and implementations you have created are really just the beginning. They form a base on which you can build your own architecture. As you can imagine, production implementations are likely to be much more sophisticated than the examples shown here, but the core concepts remain identical.