I have been working on a large project that makes pretty heavy use of a relational DB. The project is in C# and does not use an ORM. I find the application difficult to work with because of the way it accesses the DB in the application code, but I don’t have enough experience with large projects to say how it could be better (not that I think it is a good idea to change massive amounts of legacy code, but I want to know how to do it better for next project). I do not care if your answer has anything to do with C# or using an ORM or not, I just want to read various approaches taken to solve this problem.
Here is an outline of the way this project works:
- There are quite a few tables, views, and a small handful of stored procedures.
- There is a data access layer in application code that handles the raw access to the DB (this layer is a bunch of functions like would look like
GetUserById(id),GetUserByLastName(lastName),AddUser(firstname, lastName),GetCommentsByDateAndPostId(date, postId),GetCommentsByDateAndPostIdSortedByDate(date, postId), etc…). All of these functions call a hand written SQL query and return basically an in memory representation of a table (i.e.results[0].lastNameis columnlastNameof row 0). In C# this is a DataTable. - There is a layer above the data access layer for business processing rules. It is a wrapper for each of the data access functions, but may make a couple of business logic checks before calling down to the corresponding (i.e. same-named) data access function. It returns the same thing as the data access layer in all cases. Application code ONLY accesses the DB through this layer, never the data access layer directly.
- There are no one-off queries in the wild. There is a one-to-one correspondence between queries and functions in the data access layer (and thus the business logic layer). Because the database is normalized, there is a view for most queries because joins are necessary.
- There is the very rarely used stored procedure here and there
So If I want to access the DB in a new way today, I have to modify the DB, then create a data access layer function that calls a custom written SQL query to the database, then create a business logic function that calls the data access layer function. And then possibly modify a bunch of existing functions to include this change or not. I don’t even know where to begin automated testing in such a volatile environment.
And that is just if I want to modify or add a single column of a DB. If I want to add a new table there are a bunch of new functions to add for all the ways it can be selected (combinations of WHERE clauses), or inserted into, or updated by, or deleted by, or sorted by, etc.
What you describe is not a problem per se. It is in fact a good example of application design and pattern usage. What it lacks that makes it seem problematic, is that it doesn’t leverage newer technologies / techniques which aid in maintainability.
For example, from your description, it’s apparent the architecture clearly separates functional responsibilities into layers. You have a Presentation (UI) which communicates with a Domain (BLL) which in turn uses a Repository pattern to communicate with its Infrastructure (DAL). Your BLL seems to already implement cross-cutting concerns such as Validation and Security.
What you can do to improve upon this design would be to incorporate a stronger Domain by including a Model. Drop the old ADO.NET DataTable techniques and design a strongly-typed model which reflects your database. Incorporating an ORM can aid in this tremendously as it has the power to generate the model from the database and maintain changes easily.
I won’t get into further ORM advantages as you desire. Your DAL should return POCOs and Enumerables. Let your BLL return response objects (I like to call them service response objects or presentation transfer objects) which may contain things like: POCO data, Error Handling Results, Validation Results.
Another possible solution is to change your implementation of Repository pattern to a Generic Repository, though this now bleeds your infrastructure logic into the BLL. For instance, instead of:
You can create (using generics) a repository which implements IQueryable. Look at nCommon for a nice approach to this. This will allow you to do something like:
The pros to this is that you only need to create domain business logic. If you need to modify your database tables, you only need to change your business logic once. However, that query now exists in the business logic and simply uses a “Repository” as a medium to communicate to your database which some deem improper.
UPDATE
You can use generics for creating a simple response object. Example:
This class is a basic response object which I use to return results from my domain to my service layer or to my presentation. It can be serialized and supports MS Enterprise Library Validation Block. To support validation, it uses AutoMapper to translate Microsoft’s validation results to my own ValidationResult object. I don’t recommend trying to serialize MS’s classes as it proved error prone when using in services.
The overloaded constructors allow you to provide a single poco or enumerable of pocos. POCOs vs DataTables … anytime you can use strongly typed objects, it’s always better. With T4 templating your POCO can automatically be generated from an ORM model. POCOs can also be easily mapped into DTOs for service operations and vice-versa. There’s also no real need for DataTables anymore. Instead of List, you can use BindingList for CRUD support with databinding.
Returning a POCO without all its properties filled is perfectly fine. In Entity Framework this is referred to as projection. Usually I’ll create custom DTOs for this instead of my domain entities.
UPDATE
Example ValidationResult class:
Example AutoMapper code for translating Microsoft Validation Results to a ValidationResult DTO: