1Tier, 2Tier, 3Tier, n
I wrote this blog in January 2010 - which means, as per usual, I've been constantly busy for eleven months. I started it during the Christmas break and fully intended to publish it a few weeks later when I had time to write some code examples. Albeit, that free time eluded me and this text has sat here in my blog gathering dust for some time.
I initially wrote this out of frustration. At the time I was consulting with a global energy supplier who were in the process of expanding their development team and therefore wanted to create a set of generic base classes and assemblies for all of their future projects. Unfortunately the team were lacking in the experience and understanding in nTier fundamentals, which proved to make explanation and implementation difficult.
Being a developer of the self-taught ilk, nTier architecture is something I discovered quite late in my career. My background is in Classic ASP scripting, Visual Basic 6 and fairly advanced T-SQL development. The first few companies I worked for provided very little support for industry standard programming patterns and practices or any real development languages or frameworks - I usually worked on my own or managed a small team with little direction. Since discovering the world of self-employment again (I was a director of the first web development business I was involved in before the bubble burst and I went permanent for a number of years), I have thoroughly enjoyed, embraced and assimilated many different design patterns and practices, including a number of different implementations of nTier. I've also moved heavily in to Object Orientation, the .Net Framework and C#, though I still develop in VB.Net from time to time, depending on requirements.
nTier is not a new concept by any stretch of the imagination, and there are literally millions of articles on the 'net that provide models, practices and suggestions on how to approach the subject. This is another article that will no doubt be lost in the 26 million or so that came up in my recent search, though it is more for me rather than anyone else and will hopefully allow me to think through some problems I've come across. I have used very little reference material, so this article is mainly formed from my experience and knowledge. I expect there will be a few mistakes :)
What is nTier?
So, after babbling about myself for 5 minutes - what exactly is nTier? nTier is the separation of logical programming components (called "layers" or "tiers") in an object orientated programming environment. Layers and Tiers are synonymous, although some experts argue that there are subtle differences. At this level, I don't think it matters too much, so I will use them interchangeably.
The main benefits in the separation of logical programming components are:
It may be a case of separating the development or ownership of these layers in to different development teams. These reasons only really become apparent when working in large development departments with structured teams and strictly followed practices, but are valid benefits nonetheless. Developers who work for smaller companies rarely get to see this kind of environment as it's commonplace to trust their developers with all elements of a project, and they put little onus on the separation and concentration of skills.
There are of course a few downsides to an nTier approach. Namely the initial length of time it takes to implement and the somewhat overwhelming complexity of such an architecture to the inexperienced developer. Sometimes nTier is overkill, though in my opinion there is at least one design pattern or architecture suitable for each situation - even if it's just a logical separation of database access code from the front end code. Some will disagree, but the power that a tightly programmed nTier architecture can provide you is unfathomable to the layman developer.
Tiers
Let's move on to the tiers, or layers as they are also known. To anyone with at least a bit of experience in academic maths, the term "n" will be familiar... "n"Tier means just that - it is a general term to state that there are an undetermined number of tiers (n) involved in the architecture. The n represents an integer - the whole point of an nTier architecture is to enable the reuse of the lower tiers for use in other applications without the requirement for the upper tiers.
Tier structure is shrouded in ambiguity. It's almost guaranteed that no two developers will have the same idea about nTier, and if they do each one will almost always use different terminology and / or methods. Every single bespoke implementation I've worked with has had a different number of layers and has been implemented completely differently. Most have fundamental flaws due to the fact new developers have added to the architecture without fully understanding the code or the initial design hasn't been thought through.
I see the design of an architecture much the same way I see the design and normalisation of a database; you may need to spend a bit more time on the initial analysis and development, but it will pay dividends in the long run, especially in high output or robust commercial environments. Investing time in a solid nTier back end will enable you or your development team to cut out most of the mundane development involved with new solutions.
The most common approach is a 3 tier architecture. This consists of a Data Layer, a Business (or Logic) Layer and a Presentation Layer. Although I am not very keen on the 3 tier approach (for reasons that will become apparent), I will briefly summarise these layers now.
Data Layer - Consists of a data store. Typically a relational database, but could also be a series of flat files, XML files, web services or even serialized objects from another environment. There are literally no restrictions on where your data can come from, as long as it can be consumed by .Net.
Business Layer - Consists of business objects that contain methods and properties related to a specific business entity e.g. a User object. This layer depends on the Data Layer and uses this layer to populate itself.
Presentation Layer - Depending on the application this could be a Winform application, a WPF application, a Webform (ASP.NET) application, a Windows or WCF Service or maybe even all of the above. This layer depends only on the Business layer and does not access the data layer whatsoever. It is solely responsible for the display of data and should only include code to manipulate and populate the GUI.
My preferred solution isn't much different but does require a little more work. In my opinion, it will cater for the vast majority of enterprise level applications with ease. The below tier structure is what I will base the rest of this article on:
Data Store Layer (DSL) - Is the actual raw data to be accessed. A database, flat file, xml file, serialized object etc. I'm very strict with my database design - all database objects are written by hand, including tables, stored procedures, indexes, constraints, maintenance jobs and referential integrity.
Lazy database design only causes issues later down the line. Generally I develop .Net nTier solutions on top of SQL Server databases, but I have also worked with Oracle, Sybase, DB2, Microsoft Access, flat files, XML files, serialized objects and within a Service Orientated Architecture (SOA) using web services.
I sometimes include a certain level of business logic within my DSL which is frowned upon by some. Generally my stored procedures do all of the complicated data manipulation required by the Business Logic Layer (BLL) - if there's any data cleansing or manipulation, I do it before it comes out of the database. This usually makes the Presentation Layer (PL) a lot simpler and it's much more efficient to populate a User Interface with a DataSet that contains only the information you want to display, rather than pulling thousands of rows of data in to your application from a database, then filtering it and manipulating it within your code.
A good example would be an Internet forum - say a particular forum category has 500,000 posts over the space of 5 years, but you only need to display 50 per page. Pulling 500,000 posts in to your application is madness. Having to work out which 50 records should be displayed based on the page number is a massive overhead. Firstly there's excessive I/O on the database server's disk, added database server memory usage while streaming all the data from disk in to memory, excessive network bandwidth used in the transfer of that data from the database server to the application server - which is in turn stored in memory on the application server, then additional memory usage and CPU usage on the application server to filter and manipulate the data. I always recommend carrying out data manipulation in the DSL, and haven't yet found a technology capable of extracting and manipulating data as efficiently as the methods I use.
Data Access Layer (DAL) - Contains all of the core data access code. This layer does not rely on any other layers - it is an abstraction layer that exposes generic types in order to access a defined data source. A data provider needs to be written for each type of data store to be accessed, which informs the generic objects in the DAL what to do for each particular type of data store layer. The inherited types from the DAL would expose the same members regardless of data provider (unless the types are extended by the data provider implementation). The specified data provider would deal with the objects and code specific for each data provider, without touching the base objects.
This layer also contains the two base inheritable objects for your business entities - your core object and core collection. These abstract objects have been named differently in every implementation I've seen, but the principal has been roughly the same. The core object represents a single business entity, whereas the core collection represents a group of business entities. These objects are as generic as it gets - providing base functionality for each one of your business objects, such as "Save", "Update" and "Delete" etc. The objects are closely coupled with the data provider... which should provide the ability to change your data source with minimal effort - all you would need to do is build another data provider, change the provider in your business objects and you're away!
Business Logic Layer (BLL) - The BLL (sometimes confused with the Application Logic Layer (ALL)) holds the business entities that are to be used by the upper layers. These objects inherit from the aforementioned core object and core collection objects and should represent any business level entities and collections. In its most basic form, this layer could consist of an object orientated relational model of the data store layer, but it is more robust to create business related objects and methods that relate directly to those objects.
This tier is sometimes separated out in to two areas depending on the client. Some clients like to separate the code which accesses the DAL from the code that will be called by the Presentation Layer (PL), effectively creating "Data Objects" and "Business Objects" within the BLL itself. While I can see certain benefits of this (better preservation of the BLL's objects and a complete separation of any data related code from the business entities, along with possible MVC/MVP implementations), I believe it is overkill for a standard nTier architecture so I will not be looking in to this.
A layer referred to as the Application Logic Layer (ALL) could be added between the BLL and the PL to further separate business and application level logic from each other. I rarely see the need for this layer - usually the PL can incorporate such logic without detriment to the rest of the tier structure. I suppose an ALL may be appropriate when using the same application logic for multiple presentation layers - i.e. an application that has two identical PLs in different formats, such as a Webform front end and a Winform front end, which are delivered to different types of users throughout a company.
Presentation Layer (PL) - This is the highest layer in the architecture and it represents the UI or service application that is exposed to the operating system. This layer is responsible for any UI display and uses the layer immediate below it to do so (the BLL or the ALL). No data access code or data storage should be written in to this layer - we are only interested in the display here.
The main differences between my model and the 3 tier approach is the fundamental separation of the DAL and the DSL - my DAL is generic - it does not rely on any domain specific data structure... I write "Data Providers" within my DAL, which my BLL then uses to populate my business entities. When working in an environment where you may be using the same DAL on multiple different databases, for different applications, the benefits of writing this way becomes apparent.
Extensibility
nTier layers should only ever depend on the layer directly below them. The main reason for this being that each layer (usually the DSL, DAL and BLL) may be required in more than one application, but other reasons include the ability to manage each layer individually or change the technology in a single layer whilst leaving the remaining layers alone. Once you've spent time writing a BLL it can then be included in any application that uses that business logic.
I have worked with nTier code where the PL implements types defined in the DAL. This is a big no-no... it may be a case where public methods are exposed in your BLL that return types defined in the DAL. Some developers have argued with me until they're blue in the face that this kind of thing is acceptable in an nTier solution, usually because they can't see a way around what they've coded. Every scenario can be overcome with the tools and functionality that .Net provides, sometimes you just need to think outside of the box!
There is, however a caveat here - with .Net inheritance, the compiler needs to have access to the core objects you inherit from when you instantiate a business object. i.e. when a MyCompany.Clients.MyClient.User object inherits from MyCompany.Core.Data.Entity object. Any layers that use your BLL must also reference (note reference, not implement) the DAL. This is a good argument for the aforementioned ALL implementation - the ALL sits between the BLL, references the DAL and exposes types that implement IEnumerable to allow the PL to work without having to reference the DAL.
Time for a quick example. Say we have a huge asset management database written in Oracle. This database holds a huge amount of data, all tables are normalised and maintained by an expert DBA team - basically, the database is in tip top condition. Because the database holds so much relational information, many applications have been written that interface with the database:
Asset Management Application (Winform app) - The main application written for use by an asset management team to manage their daily tasks - register and distribute assets, create and manage crews / gangs (groups of people to carry out work related to the assets), register controlled substances etc... the list is endless.
Reporting Application (Webform app) - An ASP.Net application that deals with the generation and distribution of reports to management personnel who do not need the functionality of the Asset Management Application, but require quick reports to help them with decision making.
Data Collector Application (Windows service) - A windows service whose sole job is to collect information from various external data sources - Web Services, flat file imports, job updates from 3rd party clients etc. Basically a service that is constantly running in the background unattended, doing the menial administration work of data distribution that requires no user input.
Mobile PDA Application (Windows Mobile application) - A smart-phone application for sales people or crews to enable them to retrieve immediate status updates on assets or update the system with the status of a job.
Of course, I am over-simplifying the commercial aims / requirements of the solution, but hopefully it gives you a decent idea of what an enterprise application entails. The point I am trying to make is that each one of the above applications (plus additional applications that may exist / may be required in the future) access the same BLL, and as such the nTier approach is ideal due to the amount of different applications (each with their own PL) that exist that need access to a single Oracle database.
That just about does it for my first ever technical blog post. My next article will briefly look at namespaces and assemblies before I move on to giving you some examples of how I would implement an .Net nTier solution from scratch.
I initially wrote this out of frustration. At the time I was consulting with a global energy supplier who were in the process of expanding their development team and therefore wanted to create a set of generic base classes and assemblies for all of their future projects. Unfortunately the team were lacking in the experience and understanding in nTier fundamentals, which proved to make explanation and implementation difficult.
Being a developer of the self-taught ilk, nTier architecture is something I discovered quite late in my career. My background is in Classic ASP scripting, Visual Basic 6 and fairly advanced T-SQL development. The first few companies I worked for provided very little support for industry standard programming patterns and practices or any real development languages or frameworks - I usually worked on my own or managed a small team with little direction. Since discovering the world of self-employment again (I was a director of the first web development business I was involved in before the bubble burst and I went permanent for a number of years), I have thoroughly enjoyed, embraced and assimilated many different design patterns and practices, including a number of different implementations of nTier. I've also moved heavily in to Object Orientation, the .Net Framework and C#, though I still develop in VB.Net from time to time, depending on requirements.
nTier is not a new concept by any stretch of the imagination, and there are literally millions of articles on the 'net that provide models, practices and suggestions on how to approach the subject. This is another article that will no doubt be lost in the 26 million or so that came up in my recent search, though it is more for me rather than anyone else and will hopefully allow me to think through some problems I've come across. I have used very little reference material, so this article is mainly formed from my experience and knowledge. I expect there will be a few mistakes :)
What is nTier?
So, after babbling about myself for 5 minutes - what exactly is nTier? nTier is the separation of logical programming components (called "layers" or "tiers") in an object orientated programming environment. Layers and Tiers are synonymous, although some experts argue that there are subtle differences. At this level, I don't think it matters too much, so I will use them interchangeably.
The main benefits in the separation of logical programming components are:
- Increased production rates / to enhance Rapid Application Development (RAD).
- To allow for maximum code re-use throughout multiple projects.
- To standardise programming practices within a team.
- To aid in physically distributing enterprise application load over multiple servers.
- To allow development to maintain each layer separately, including version control and technology upgrades.
It may be a case of separating the development or ownership of these layers in to different development teams. These reasons only really become apparent when working in large development departments with structured teams and strictly followed practices, but are valid benefits nonetheless. Developers who work for smaller companies rarely get to see this kind of environment as it's commonplace to trust their developers with all elements of a project, and they put little onus on the separation and concentration of skills.
There are of course a few downsides to an nTier approach. Namely the initial length of time it takes to implement and the somewhat overwhelming complexity of such an architecture to the inexperienced developer. Sometimes nTier is overkill, though in my opinion there is at least one design pattern or architecture suitable for each situation - even if it's just a logical separation of database access code from the front end code. Some will disagree, but the power that a tightly programmed nTier architecture can provide you is unfathomable to the layman developer.
Tiers
Let's move on to the tiers, or layers as they are also known. To anyone with at least a bit of experience in academic maths, the term "n" will be familiar... "n"Tier means just that - it is a general term to state that there are an undetermined number of tiers (n) involved in the architecture. The n represents an integer - the whole point of an nTier architecture is to enable the reuse of the lower tiers for use in other applications without the requirement for the upper tiers.
Tier structure is shrouded in ambiguity. It's almost guaranteed that no two developers will have the same idea about nTier, and if they do each one will almost always use different terminology and / or methods. Every single bespoke implementation I've worked with has had a different number of layers and has been implemented completely differently. Most have fundamental flaws due to the fact new developers have added to the architecture without fully understanding the code or the initial design hasn't been thought through.
I see the design of an architecture much the same way I see the design and normalisation of a database; you may need to spend a bit more time on the initial analysis and development, but it will pay dividends in the long run, especially in high output or robust commercial environments. Investing time in a solid nTier back end will enable you or your development team to cut out most of the mundane development involved with new solutions.
The most common approach is a 3 tier architecture. This consists of a Data Layer, a Business (or Logic) Layer and a Presentation Layer. Although I am not very keen on the 3 tier approach (for reasons that will become apparent), I will briefly summarise these layers now.
Data Layer - Consists of a data store. Typically a relational database, but could also be a series of flat files, XML files, web services or even serialized objects from another environment. There are literally no restrictions on where your data can come from, as long as it can be consumed by .Net.
Business Layer - Consists of business objects that contain methods and properties related to a specific business entity e.g. a User object. This layer depends on the Data Layer and uses this layer to populate itself.
Presentation Layer - Depending on the application this could be a Winform application, a WPF application, a Webform (ASP.NET) application, a Windows or WCF Service or maybe even all of the above. This layer depends only on the Business layer and does not access the data layer whatsoever. It is solely responsible for the display of data and should only include code to manipulate and populate the GUI.
My preferred solution isn't much different but does require a little more work. In my opinion, it will cater for the vast majority of enterprise level applications with ease. The below tier structure is what I will base the rest of this article on:
Data Store Layer (DSL) - Is the actual raw data to be accessed. A database, flat file, xml file, serialized object etc. I'm very strict with my database design - all database objects are written by hand, including tables, stored procedures, indexes, constraints, maintenance jobs and referential integrity.
Lazy database design only causes issues later down the line. Generally I develop .Net nTier solutions on top of SQL Server databases, but I have also worked with Oracle, Sybase, DB2, Microsoft Access, flat files, XML files, serialized objects and within a Service Orientated Architecture (SOA) using web services.
I sometimes include a certain level of business logic within my DSL which is frowned upon by some. Generally my stored procedures do all of the complicated data manipulation required by the Business Logic Layer (BLL) - if there's any data cleansing or manipulation, I do it before it comes out of the database. This usually makes the Presentation Layer (PL) a lot simpler and it's much more efficient to populate a User Interface with a DataSet that contains only the information you want to display, rather than pulling thousands of rows of data in to your application from a database, then filtering it and manipulating it within your code.
A good example would be an Internet forum - say a particular forum category has 500,000 posts over the space of 5 years, but you only need to display 50 per page. Pulling 500,000 posts in to your application is madness. Having to work out which 50 records should be displayed based on the page number is a massive overhead. Firstly there's excessive I/O on the database server's disk, added database server memory usage while streaming all the data from disk in to memory, excessive network bandwidth used in the transfer of that data from the database server to the application server - which is in turn stored in memory on the application server, then additional memory usage and CPU usage on the application server to filter and manipulate the data. I always recommend carrying out data manipulation in the DSL, and haven't yet found a technology capable of extracting and manipulating data as efficiently as the methods I use.
Data Access Layer (DAL) - Contains all of the core data access code. This layer does not rely on any other layers - it is an abstraction layer that exposes generic types in order to access a defined data source. A data provider needs to be written for each type of data store to be accessed, which informs the generic objects in the DAL what to do for each particular type of data store layer. The inherited types from the DAL would expose the same members regardless of data provider (unless the types are extended by the data provider implementation). The specified data provider would deal with the objects and code specific for each data provider, without touching the base objects.
This layer also contains the two base inheritable objects for your business entities - your core object and core collection. These abstract objects have been named differently in every implementation I've seen, but the principal has been roughly the same. The core object represents a single business entity, whereas the core collection represents a group of business entities. These objects are as generic as it gets - providing base functionality for each one of your business objects, such as "Save", "Update" and "Delete" etc. The objects are closely coupled with the data provider... which should provide the ability to change your data source with minimal effort - all you would need to do is build another data provider, change the provider in your business objects and you're away!
Business Logic Layer (BLL) - The BLL (sometimes confused with the Application Logic Layer (ALL)) holds the business entities that are to be used by the upper layers. These objects inherit from the aforementioned core object and core collection objects and should represent any business level entities and collections. In its most basic form, this layer could consist of an object orientated relational model of the data store layer, but it is more robust to create business related objects and methods that relate directly to those objects.
This tier is sometimes separated out in to two areas depending on the client. Some clients like to separate the code which accesses the DAL from the code that will be called by the Presentation Layer (PL), effectively creating "Data Objects" and "Business Objects" within the BLL itself. While I can see certain benefits of this (better preservation of the BLL's objects and a complete separation of any data related code from the business entities, along with possible MVC/MVP implementations), I believe it is overkill for a standard nTier architecture so I will not be looking in to this.
A layer referred to as the Application Logic Layer (ALL) could be added between the BLL and the PL to further separate business and application level logic from each other. I rarely see the need for this layer - usually the PL can incorporate such logic without detriment to the rest of the tier structure. I suppose an ALL may be appropriate when using the same application logic for multiple presentation layers - i.e. an application that has two identical PLs in different formats, such as a Webform front end and a Winform front end, which are delivered to different types of users throughout a company.
Presentation Layer (PL) - This is the highest layer in the architecture and it represents the UI or service application that is exposed to the operating system. This layer is responsible for any UI display and uses the layer immediate below it to do so (the BLL or the ALL). No data access code or data storage should be written in to this layer - we are only interested in the display here.
The main differences between my model and the 3 tier approach is the fundamental separation of the DAL and the DSL - my DAL is generic - it does not rely on any domain specific data structure... I write "Data Providers" within my DAL, which my BLL then uses to populate my business entities. When working in an environment where you may be using the same DAL on multiple different databases, for different applications, the benefits of writing this way becomes apparent.
Extensibility
nTier layers should only ever depend on the layer directly below them. The main reason for this being that each layer (usually the DSL, DAL and BLL) may be required in more than one application, but other reasons include the ability to manage each layer individually or change the technology in a single layer whilst leaving the remaining layers alone. Once you've spent time writing a BLL it can then be included in any application that uses that business logic.
I have worked with nTier code where the PL implements types defined in the DAL. This is a big no-no... it may be a case where public methods are exposed in your BLL that return types defined in the DAL. Some developers have argued with me until they're blue in the face that this kind of thing is acceptable in an nTier solution, usually because they can't see a way around what they've coded. Every scenario can be overcome with the tools and functionality that .Net provides, sometimes you just need to think outside of the box!
There is, however a caveat here - with .Net inheritance, the compiler needs to have access to the core objects you inherit from when you instantiate a business object. i.e. when a MyCompany.Clients.MyClient.User object inherits from MyCompany.Core.Data.Entity object. Any layers that use your BLL must also reference (note reference, not implement) the DAL. This is a good argument for the aforementioned ALL implementation - the ALL sits between the BLL, references the DAL and exposes types that implement IEnumerable to allow the PL to work without having to reference the DAL.
Time for a quick example. Say we have a huge asset management database written in Oracle. This database holds a huge amount of data, all tables are normalised and maintained by an expert DBA team - basically, the database is in tip top condition. Because the database holds so much relational information, many applications have been written that interface with the database:
Asset Management Application (Winform app) - The main application written for use by an asset management team to manage their daily tasks - register and distribute assets, create and manage crews / gangs (groups of people to carry out work related to the assets), register controlled substances etc... the list is endless.
Reporting Application (Webform app) - An ASP.Net application that deals with the generation and distribution of reports to management personnel who do not need the functionality of the Asset Management Application, but require quick reports to help them with decision making.
Data Collector Application (Windows service) - A windows service whose sole job is to collect information from various external data sources - Web Services, flat file imports, job updates from 3rd party clients etc. Basically a service that is constantly running in the background unattended, doing the menial administration work of data distribution that requires no user input.
Mobile PDA Application (Windows Mobile application) - A smart-phone application for sales people or crews to enable them to retrieve immediate status updates on assets or update the system with the status of a job.
Of course, I am over-simplifying the commercial aims / requirements of the solution, but hopefully it gives you a decent idea of what an enterprise application entails. The point I am trying to make is that each one of the above applications (plus additional applications that may exist / may be required in the future) access the same BLL, and as such the nTier approach is ideal due to the amount of different applications (each with their own PL) that exist that need access to a single Oracle database.
That just about does it for my first ever technical blog post. My next article will briefly look at namespaces and assemblies before I move on to giving you some examples of how I would implement an .Net nTier solution from scratch.