lørdag 16. januar 2010

Object Oriented design and Relational Databases

For as long as I have worked with object oriented languages there has always been a bit awquard working with relational databases. I have grown up in the Microsoft world with visual foxpro/vb/.net/access/ms sql server and then used ado, ado.net and now ORM. Seeing Udi Dahan's talk on"Command Query Responsibility Segregation" and reading Eric Evans book "Domain Driven Design" made me connect some dots leading me to write this post.

So why do we use relational databases? If the only purpose of the database is to persist the domain models entities we would have used an object oriented database right? No transformation between tables and objects would be needed. Ok, given this scenario we're sitting here with our clean entity objects formed in a way that perfectly satisfies the needs for rule validation and process execution performed by the domain model. Brilliant, just the way we like it! Enter the UI. Now this is where it gets ugly. The user requires information to be presented in a way that is humanly readeable. The domain model is perfectly happy with knowing that the customer entity with id 1432 links to the address entity with id 65423. To the person using the application that would be a useless peice of information. The structure of the information needed by the user is often very different to the entities needed by the domain model. Specially when the user needs some kind of grouping of information or statistics. These type of complex queries wants to gather information spaning multiple entites joining them in ways unnatural to the domain model. This is where the relational database comes and performs it's magic. With a relational database we can easily perform complex queries joining multiple tables and tweaking information to fit our needs.

Above is the traditional way of looking at layered architecture. I find this way of viewing layered architecture a bit deceiving. So what about the issue described above with the UI geting in the mix. How do we often solve this problem? Well sadly the domain model often gets to pay the price. Our clean entities are stretched and pulled and lumps of information are attached to them so that we can pass them on to the UI. These sins are committed in the name of Layered Architecture though Layered Architecture is not to blame. It's just easy to interpret the picture above that way. Wether being datasets or object entities relations and bulks of information are added, complicating both the UI and the domain. We end up having to make compromizes constantly because the entity no longer fits either the domain model or the UI in a good way.
There must be a better way! Well there is. We have already pin pointed two separate needs here. The domain model needs a database to persist it's entities to and the user needs to use the persisted information in a way that makes sence to him/her. Let's create two rules.
  1. Neither the domain model nor the UI should ever be aware of the complex structure of the database.
  2. The domain model should never be aware of the complexity of it's clients (UI in this example).
Ok, that solves two problems. The domain model's entities will no longer be compromized by it's consumers since their complexity can no longer affect it. They will also be unaware of any database complexity coming from the database because of it being fitted to coping with multiple needs. Our data abstraction layer using orm or any other data access provider will make sure of that.
Great now we have a clean, readeable and maintainable domain model again. So where does the UI retrieve it's information from then? From the database of course. And to hide the database complexity we can use a view or a stored procedure that returns the information the UI needs formatted excatcly as it needs it to be. How cool is that. We just took advantage of the power of the relational database which now hides it's complexity from it's users.

The UI now bypasses the domain model completely when retrieving it's information. This means that the way the UI makes changes to the database has changed. Earlier the UI was provided with the domain model's entities which it modified and sent back to the domain model for persisting. That is no longer possible since the domain model doesn't share it's entities. What we would want to do now is to build an abstraction between the domain model and the UI. Call it an abstraction layer or service layer. Naming is not important right now. The UI now needs to be able to persist information and execute processes through this abstraction. We need some defined messages that the UI can send to the abstraction. For example the SaveAddress operation in the abstraction needs to be able to take an AddressMessage containing address information. The abstraction then needs to use the message to persist it's information using the domain and it's entities. We then end up with a design where information flows like on the sketch below.

When creating services and abstractions it's important to think about responsibility. For instance the consumer of the service should be responsible for it's interfaces and messages while the service host should be responsible for the implementation of these interfaces. Let's look at the service layer between the UI and the model. The UI would define how the service methods and messages should look and the domain model would implement the service interface. Again for the database access framework the UI and the domain model would be responsible for defining the interface while the data abstraction component/layer would implement this interface.

To conclude this post the main takeaways are that a design like this should be viewed as three separate parts: UI, Domain Model and Data store. The implementations should respect this and make sure that each part focus on the problem it's trying to solve.
  • Domain Model - Handle the logic, rules and processes the application is supposed to handle through it's specification. This is the heart of the application.
  • UI - Make sure that the user is able to work with the applications functionality in a way suited for the human mind.
  • Relational Database - Handles persisting the Domain Model's entities and provides the UI with human readable information.

2 kommentarer:

  1. Since the UI can only read data from the data access layer, what if you need to aggregate data from different sources (databases and/or services)? Would you let the data access layer be responsible for this aggregation, or would you do multiple calls in the UI?

    As an example, a method GetContactWithContracts(int contactId) should return both Contact and Contract data. Contact data comes from one system, Contract data comes from another.

    In my opinion, the UI shouldn't have to know that the Contact and Contract data comes from different sources. And if the service layer is WCF based, the data should be aggregated and returned as a single container object to reduce the number of service calls.

    The data access layer shouldn't have to do aggregates as it should have simple repository classes with methods that does one thing, ContactRepository.GetContact(), ContractRepository.GetContracts().

    Thus I would want to have this in a another layer than the UI and data access. Though putting it in the Business logic/Domain layer can clutter it, adding a new layer will make the solution and codebase more complex. Each new layer tend to decrease the efficiency. I was a lot more productive 10 years ago writing 2 layer (UI, DataAccess) code, than I am today with UI, Service consumer, Service layer, Business Logic, Data Access...

  2. Just like you say we want to limit the UI calls down to a single call if possible. So when in a SOA where UI's as you mention would want to aggregate data from multiple sources we would want an abstraction between the UI and the complexity of the scattered data sources.

    I totally get you. Adding these abstractions and layers does complicate the code-base. Though the intent behind adding abstractions should be simplifying a already complex solution it's not always the end result. Making sure the UI uses single calls for retrieval and pushes commands for behaviors certainly simplifies the solution a bit.

    Where to place this abstraction depends on the solution. It might deserves to be it's own service. Or you could reuse the existing service and have the service depend upon a CommandProcessor and a QueryProcessor class to split commands and queries. Again, this places the query part so close to the domain model that without discipline the code-base might become cluttered.