How To: Create an N-Tier Application with the .NET Framework
J.D. Meier, Alex Homer, David Hill, Jason Taylor, Prashant Bansode, Lonnie Wall, Rob Boucher Jr, Akshay Bogawat
The N-tier application is based on the layered architecture, which organizes a software application into a set of logical layers for the purpose of managing dependencies and creating pluggable components. It is a client-server architecture, in which the presentation, business, and data access layers are spread across one or more servers. Locating the tiers on separate servers provide an advantage of upgrading or replacing the tiers independently as requirements or technology change. It also allows each tier to be managed and scaled independently improving the flexibility.
- Summary of Steps
- Step 1 – Organize The Application Into Logical Layers
- Step 2 – Organize Your Application Into Physical Tiers
- Step 3 – Choose Your Deployment Strategy
- Additional Resources
- Determine how to structure your application.
- Identify the tiers.
- Identify and choose your deployment strategy.
This article will guide you to address the important issue of creating an N-Tier application using the .NET framework. It will guide you through to create a flexible and reusable application for distribution to any number of client interfaces.
Summary of Steps
- Step 1 – Organize The Application Into Logical Layers.
- Step 2 – Organize Your Hardware Into Physical Tiers.
- Step 3 – Choose Your Deployment Strategy.
Step 1 – Organize The Application Into Logical Layers
The first step is to identify how you will structure the layers of your application. When choosing your layering strategy, you should try to minimize the complexity by separating functionality into areas of concern. You should focus on the highest level of abstraction and start by grouping functionality into layers.
Determine if you need layers
In the first step you should determine if you need to group functionality into layers. Layering provides reusability and loose coupling. But it may also impact performance and increase complexity. By not using layers, you can improve performance because everything is closely coupled. However, that coupling may also limit your ability to extend and maintain your application. In general, the gain in extensibility and maintainability provided by a layered design far outweighs the marginal improvement in performance you might gain from a close-coupled non-layered design.
Choose the layers that you need
For most business applications developed using the Microsoft .NET Framework, it makes sense to group functionality into presentation, service, business, and data access layers. The following is a description of components that you would find in each layer and when that layer may not be appropriate.
- Presentation – This layer will contain components used to process requests from the user interface and render the user interface output. If your application does not have a user interface, you do not require a presentation layer.
- Service – This layer will contain components used to handle service requests. For example, in an application that uses the Windows Communication Foundation (WCF) you would find components used to define contracts for the interface, along with components used to implement the interface and provide translation support between implementation classes and external contract classes. If your application does not expose services, you would not include a service layer in the design.
- Business – This layer will contain components used to process requests from the presentation or service layer. Examples of components you might find in this layer include business processing, business entities, business workflow, helper, and utility components. In some cases, you may not have any business logic and just pull data from a data source, which means that you may not require a business layer.
- Data Access – This layer will contain components used to manage the interaction with data sources. For example, with ADO.NET you will find connection and command components in the data access layer. If your application does not use external data, you do not need to implement a data access layer.
Determine rules for interaction between layers
You must define rules for how the layers will interact with each other. The main reason for specifying interaction rules is to minimize dependencies and eliminate circular references. As a result, a common rule to follow is to allow only one-way interaction between the layers, as discussed in the following guidelines:
- Top-down Interaction – Higher-level layers can interact with layers below, but a lower level layer should never interact with layers above. You should always enforce this rule in your design to avoid circular dependencies between layers.
- Strict Interaction – Each layer must interact with the layer directly below. This rule will enforce strict separation of concerns where each layer only knows about the layer directly below. The benefit with this rule is that modifications to a layer will only affect the layer directly above.
- Loose Interaction – Higher-level layers can bypass layers to interact with lower-level layers directly. This can improve performance, but will also increase dependencies. In other words, modification to a lower-level layer can affect multiple layers above.
Consider using strict interaction rules:
- If you are designing an application that will be modified over time to introduce new functionality, and you want to minimize the impact of those changes.
- If you are designing an application that may be distributed across different physical tiers.
Consider using loose interaction rules:
- If you are designing an application that you know will not be distributed across physical tiers; for example, a stand-alone Rich Client application that will be installed on the client.
- If you are designing a small application where changes that affect multiple layers can be managed with minimal effort.
Define the interfaces between layers
A layer should not expose internal components on which another layer could dependent. Instead, the interface to a layer should be designed to minimize dependencies by providing a public interface that hides details of the components within the layer. This hiding is called abstraction, and there are many different ways to implement it.
The following design approaches can be used to define the interface to a layer:
- Abstract Interface – This can be accomplished by defining an abstract base class, or type definition, that concrete classes implement. The base class, or type, defines a common interface that all consumers of the layer use to interact with the layer. This approach also improves testability, because you can use test objects (sometimes referred to as mock objects) that implement the abstract interface.
- Common Design Type – Many design patterns define object types that represent an interface into different layers. These object types provide an abstraction that hides details related to the layer. For example, the Table Data Gateway pattern defines object types that represent tables in a database. These objects are responsible for implementing the SQL queries required to interact with a database. Consumers of the object have no knowledge that SQL is being used, or the details of how it connects to the database and executes commands.
- Dependency Inversion – This is a programming style where abstract interfaces are defined external to, or independent of, any layers. Instead of one layer being dependent on another, both layers are dependent on common interfaces. The dependency injection pattern is a common implementation of dependency inversion. With dependency injection, components from one layer can be injected into components in another layer through the use of common abstract interfaces.
- Message-based – Instead of interacting directly with components, message-based interfaces can be used to provide interaction between layers. There are several messaging solutions, such as Web services and Microsoft Message Queuing, which support interaction across physical and process boundaries. However, you can also combine abstract interfaces with a common message type used to define data structures for the interaction.
Step 2 – Organize Your Application Into Physical Tiers
In the first step you have chosen the layering strategy for the application. In this step will show you how to organize the application into physical tiers.
Tiered distribution organizes the system infrastructure into a set of physical tiers to provide specific server environments optimized for specific operational requirements and system resource usage. A single-tiered infrastructure is not very flexible. The servers must be generically configured and designed around the strictest of operational requirements. They must support the peak usage of the largest consumers of system resources. Multiple tiers, on the other hand, enable multiple environments. You can optimize each environment for a specific set of operational requirements and system resource usage. You can then deploy components onto the tier that most closely matches their resource needs and enables them to best meet their operational requirements. The more tiers you use, the more deployment options you will have for each component.
A primary driver for optimizing component deployment is to match a component's resource consumption profile to an appropriate server. This implies that a direct mapping of layers to tiers is often not the best distribution strategy. Also keep in mind that adding more tiers adds complexity, deployment effort, and cost. One of the reasons for adding tiers is to impose security.
Step 3 – Choose Your Deployment Strategy
There are several common patterns that represent application deployment structures found in most solutions. When it comes to determining the best deployment solution for your application, it helps to first identify the common patterns. Once you have a good understanding of the different patterns you than consider scenarios, requirements, and security constraints to choose the specific pattern or patterns.
This pattern represents a basic structure with two main components, a client and a server. In this scenario, the client and server could exist on the same machine or could be divided across two machines. The example shown below represents a common web application scenario where the client interacts with a Web server.
This pattern represents a structure where components of the application can be spread across one or more servers. The following diagram represents a 2-tier deployment where all of the application code is located on the client and the database is located on a separate server.
In a 3-tier design, the client interacts with application software deployed on a separate server, and the application server interacts with a database that is also located on a separate server. This is a very common pattern for most web applications and web services.
In this scenario, the Web server is physically separated from the application server. This would normally be done for security reasons where the Web server is deployed into a perimeter network and access the application server located on a different network sub-net. In this scenario, you would normally see a firewall between the client and Web tier, and another firewall between the Web tier and application or business logic tier.
Consider Client/Server or 2-tier:
- If you are developing a client that will access an application server.
- If you are developing a stand-alone client that will access an external database.
- If you are developing an Intranet based application where all servers are located within the private network.
- If you are developing an Internet based application, and security requirements do not restrict implementing business logic on the public facing web/application server.
- If security requirements dictate that business logic cannot be deployed to the perimeter network.
- If you have application code that makes heavy use of resources on the server and you want to offload that functionality to another server.
In most cases, the recommendation is to locate all of the application code on the same server. Anytime you cross physical boundaries, performance is affected due to the fact that the data must be serialized across those boundaries. However, there are some cases where you need to split functionality across servers. In addition, depending on where servers are located, you can choose communication protocols that are optimized for performance.
Consider using distributed deployment for your Web applications if your security concerns prohibit you from deploying your business logic on your front-end Web server, consider distributed deployment. Consider using a message-based interface for your business layer. Consider using the TCP protocol with binary encoding to communicate with the business layer for best performance.
Consider using load balancing to distribute requests so that they are handled by different Web servers. Avoid server affinity when designing scalable Web applications. Design stateless components for your Web application
Rich Internet Applications (RIA)
A distributed architecture is the most likely scenario for deployment because RIA implementations move presentation logic to the client. If your business logic is shared by other applications, consider using distributed deployment. Consider using a message-based interface for your business logic.
Rich Client Applications
In an n
-tier deployment, you can place presentation and business logic on the client, or only the presentation logic on the client. The following figure illustrates the case where the presentation and business logic are deployed on the client.
The following figure illustrates the case where the business and data access logic are deployed on an application server.
For more information on layered designs and deployment scenarios, see the following resources: