"...the application I am looking at is an intranet-based line of business system written in C# / ASP.Net. The client has been having difficulty in developing the system because insufficient thought has been given to the design beforehand..."
In this post I will be using the example of a legacy application that I am re-architecting as a case study to illustrate some design traps, and I willbe pointing out some of the steps you can take to ensure you're in with a shout to get a maintainable application.
The focus of this post is about layers and tiers, and I'll be contrasting some of the mistakes I have seen with some better practice.
Layer: When I talk about layers, I am usually referring to separate assemblies that have a reference hierarchy and together comprise an application. Sometimes it is possible (although not neccessarily desirable) to combine logical layers within the same assembly.
Tier: When I talk about tiers I am usually referring to logical sections of an application that can run on physically separate machines, with the whole of the sub-applications on each tier comprising the whole application.
Communication between layers
One of the main reasons that we separate an application into layers is to provide abstraction, i.e. we want layers higher in the stack to rely on the behaviour of their references without relying on their implementation. This affords us several benefits:
a) In a project we can separate work among several developers without everyone needing to access the same piece of code.
b) We can unit test each layer separatey, so we can improve quality and more accurately identify where a problem originates.
c) We can re-use components elsewhere without needing to know their internal workings.
However, a golden rule of layered programming is this: A layer must not require any knowledge of its calling layers or other layers higher in the stack. If a layer needs to raise information back to its calling layer it raises an event and attaches the information. There should be no direct calls or we end up with circular references.
This takes me onto one of the horrors that I encountered in my latest application. In the business logic, there is a reliance on the HttpContext of the website. This is bad in a numbe of ways, but primarily this means that it will not be possible to re-use this code either if we decided to produce a version of our application as a windows application, or if we wanted to host our logic in a service where we may not have the same richness of the end-user context (profile, identity etc) that we have in a website.
Communication between tiers
When we organise code into tiers, we are separating up portions of the application and allowing them to be run on separate physical machines, with communication over a network. The reasons for this are:
a) By distributing acros separate machines we allow an application to be scaled out for increased resilience and throughput.
b) Using the principles of service orientation, we allow not only code to be shared, but the running instances of the code; our logic can be used by different systems, and through this we can build up a flexible enterprise.
The rules for dependency between tiers, especially when we use services, are not as clear cut. One of the principles of good service oriented design are that we design service interfaces and the code of the service is abstracted behind these interfaces. This is especially distilled in service orientation but the same principles also apply, even with such tier separations as SQL connections via OLEDB. Some of our tiers will have references in a way that is conceptually similar to references between layers, and yet we cannot pass an event up to the calling tier because our tiers do not run in the same physical process. However, what we do when we want to pass information back it to pulish a message. In a tiered architecture messages are what makes it all tick.
Of course, there are some SOA patters where there is a communication protocol between applications that are bidirectional, but this isn't really within the scope of what I am talking about here.
There is another horror story here, and it lies in the way the previous developer had integrated with other applications, notably the CRM system. The develper used a cross-database call to access the CRM system When my client decided to change their CRM system they did find that the application broke straight away. If the application had relied on a CRM search service then the change of CRM system would only have required a replacement service and there would be no internal change in the application.
The role of entities
Another thing I have been grappling with is the role of entities in the application I am working on. Entities are a way of structuring data so that it can be communicated between layers or tiers:
Data entities allow communication between a database and a data access layer. [Another horror story here: I have come across so many issues when data entities are passed throughout an application, bound to a UI etc. They should not really propagate outside of the data access layer.]
Business entities are the data that business logic understands. Usually my interface for a data access layer will read / write business entities and the translation to the data entitie will take place inside the data access layer. The business entities will also be what the business logic interfaces use. As such, entities are not so much a layer, but something that allows layers to communicate.
Data contracts are used for service communications in the WCF model, and allow the client application to communicate with the service tier. Again, usually there will be some translation between the business entities and the data contracts.
View model entities are the entities used by a UI. In an n-tier architecture these will be the entities that the UI binds on to. There will need to be a translation layer between either the business entities or the data contracts, depending on whether we are calling business logic in the same process or between tiers or in separate processes.
It is important that we understand which entities we need to use, because we can then start to design applications that have less dependence and coupling between layers and tiers. This allows us to build components that use the entities at the correct level of abstraction.
Where did it go wrong?
In the application I have been re-architecting the main problem lies in the fact that there is a single database, and a single entity model based on NHibernate. These data entities are then used throughout the application, in the UI, in the business logic, in the data access. This means that the UI is directly coupled t the database, and also directly coupled to other systems that the application integrates with. Everything is coupled.
In thinking about this I have realised more how important the entity model is in an aplication and how central to the development it is. In my application I have decided to use the database entities as the core of my application going forward, i.e. as the prototype of my business entities, and I am then building a data access layer on LINQ with a translation layer between the LINQ entities and the business entities. Having separated the business entities from the data access I have then put in a business logic layer, abstract the UI from the data and start to get some sort of order on the application.
I don't have enough time (i.e. customer's money) to rebuild the application any more than this, but the creation of a business entity model and a business logic interface is the core thing I needed to put in place to have a chance of making any sense of this application.
Now that I have put the first steps in place to provide some separation between my UI and my logic, next I need to look at how I can create pluggable logic so that I can handle the different services sold by my client in a generic way.