How To: Structure Your Application

J.D. Meier, Alex Homer, David Hill, Jason Taylor, Prashant Bansode, Lonnie Wall, Rob Boucher Jr, Akshay Bogawat.

Applies To

  • Software design and the architecture used by your application.

Summary

Software Architecture is often defined as the structure or structures of an application. When defining these structures, the goal of a software architect is to minimize the complexity by separating functionality into areas of concern using different levels of abstraction. You start by examining the highest level of abstraction while identifying different areas of concern. As the design evolves, you dive deeper into the levels expanding the areas of concern until you have defined all of the structures.

Contents

  • Objectives
  • Overview
  • Summary of Steps
  • Step 1 – Choose Your Layering Strategy
  • Step 2 – Identify Cross Cutting Concerns
  • Step 3 – Define the Interfaces between Layers
  • Step 4 – Choose Your Deployment Strategy
  • Step 5 – Choose Communication Protocols
  • Additional Resources

Objectives

  • Identify and choose a layering strategy.
  • Define the interface between layers.
  • Identify and choose a deployment strategy.
  • Choose appropriate communication protocols.

Overview

When starting to design an application, your first task is to focus on the highest level of abstraction and start by grouping functionality into layers. Next, you must define the public interface for each layer, which depends on the type of application you are designing. Once you have defined the layers and interfaces, you must determine how the application will be deployed. Finally, you choose the communication protocols to use for interaction between the layers and tiers of the application.

Summary of Steps

  • Step 1 – Choose Your Layering Strategy.
  • Step 2 – Identify Cross Cutting Concerns
  • Step 3 – Define the Interfaces between Layers
  • Step 4 – Choose Your Deployment Strategy
  • Step 5 – Choose Communication Protocols

Step 1 – Choose Your Layering Strategy

Layers represent a logical grouping of components into different areas of concern. For example, business logic represents an area of concern that should be grouped into a layer. This is probably the most important decisions you will make during the initial design phase of your application. Upon completion of this step, you should have a good understanding of how you want to structure the layers of your application.

There are many different ways to group functionality into layers. The following diagram illustrates the layers that are common in most business application designs. In this example, the functionality is grouped by presentation logic, service logic, business logic, and data access logic. If services are not exposed, the service layer would not be required and you would have just the presentation, business, and data access layers.

Layers.JPG
This is just an example, and not the only way to layer an application. However, layering in general is common in most software designs. Low-level operating system designs use a "ring" approach where ring 0 (the kernel) represents the lowest layer with direct access to hardware resources such as the CPU and memory. Device drivers sit in outer rings and interact with the hardware through interfaces provided by ring 0. Application code is contained in the outermost ring, which interacts with hardware through device drivers.

When choosing a layering strategy for your application, consider the following key points:
  • Determine if you need layers.
  • Choose the layers that you need.
  • Determine if you need to relax layers.
  • Determine rules for interactions between layers.

Determine if you need layers

In most cases, you should group functionality into layers. However, there are some exceptions where it may not be necessary. Crossing layer boundaries, especially boundaries between physically remote components, imposes a performance overhead. You must consider the trade-off of reusability and loose coupling that layers provide against their impact on performance and the increase in complexity. For example, if you are building a very small application, you might consider implementing presentation, business logic, and data access functionality within user interface code. In addition, there are some software engineers who feel that the overhead of crossing layer boundaries or adding layers will have a negative impact on performance.
In the end, the decision to use layers comes down to performance versus maintainability. 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

There are several different ways to group functionality into layers. For example, one way is to use presentation, service, business, and data access layers. Many object-oriented designs that focus on the business domain use a different layering strategy, with the lowest layer used for infrastructure code that provides support for functions such as data access. The next highest layer is a domain layer that contains business domain entities. Finally, a higher layer contains application code.

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 if you need to relax layers

In some cases, it makes sense to collapse or relax layers. For example, an application with very limited business rules, which focus mainly on validation, might implement both the business and presentation logic in a single layer.

Consider the following guidelines for relaxing layers:
  • If you have limited business rules, it may make sense to implement business and presentation logic within the same layer.
  • If you have an application that pulls data from a service and displays that data, it may make sense to add service references directly to the presentation layer and consume the service data directly. In this case, you might combine data access with presentation.
  • If you have a data access service that provides direct access to views or tables in a database, you might consider implementing the data access functionality within a service layer.

These are just some examples of where it might make sense to relax layers. However, the general rule is that you should always group functionality into layers. In some cases, one layer may act as a pass-through layer without providing a great deal of functionality. However, by separating that functionality, you can extend it later with little or no impact to other layers in the design.

Determine rules for interaction between layers

When it comes to a layering strategy, 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. For example, if two layers each have a dependency on components in the other layer you have introduced a circular dependency. 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.

Check Point

Before you finish this step, you should be able to answer the following questions:
  • Have you identified the functionality required by your application and grouped that functionality into layers?
  • Have you defined rules for how layers will interact with each other?

Step 2 – Identify Cross Cutting Concerns

After you define the layers, you must identify the functionality that spans layers. This functionality is often described as cross cutting concerns, and includes functionality such as logging, caching, validation, authentication, and exception management. There are different approaches to handling this functionality, from common libraries such as the patterns & practices Enterprise Library to Aspect Oriented Programming (AOP) methods where metadata is used to insert cross cutting code directly into the compiled output.

The following guidelines will help you implement components to manage cross cutting concerns:
  • Examine the functions required in each layer, and look for cases where you can abstract that functionality into common components, perhaps even general-purpose components that you configure depending on the specific requirements of each section of the application. It is likely that these kinds of components will be reusable in other applications.
  • Depending on how you physically distribute the components and layers of your application, you may need to install the cross cutting components on more than one physical tier. However, you still benefit from reusability and reduced development time and cost.
  • Consider using the Dependency Injection pattern to inject instances of cross cutting components into the sections of your application based on configuration information. This allows you to change the cross cutting components that each section uses easily, without requiring recompilation and redeployment of your application.
  • Consider using a third-party library of components that are highly configurable and can reduce development time. An example is the patterns & practices Enterprise Library, which contains application blocks designed to help you implement caching, exception handling, authentication and authorization, logging, exception handling, validation, and cryptography functions. It also contains mechanisms that implement policy injection and a dependency injection container that make it easier to implement solutions for a range of cross cutting concerns.

Check Point

Before you finish this step, you should be able to answer the following questions:
  • Have you identified any cross cutting concerns?
  • Have you decided how you will implement solutions for these concerns?

Step 3 – Define the Interfaces between Layers

When it comes to defining the interface for a layer, the primary guidance is to enforce loose coupling between layers. What this means is that 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.

The key difference with a message-based interface is that the interaction between layers uses a common structure that encapsulates all the details of the interaction. This structure can define operations, data schemas, fault contracts, security information, and many other structures related to communication between layers.

Consider using an abstract interface:
  • If you want the ability to implement different behavior with concrete instances of the interface.
  • If you want to improve the testability of your application through the use of mock objects.

Consider using common design types:
  • If you are implementing a design pattern for the interface within your layer. Many design patterns are based on abstract interfaces. However, some are based on concrete classes.
  • If you want a fast and easy way to implement the interface within your layer. Most appropriate patterns such as Table Data Gateway are well documented in this scenario.

Consider using Dependency Inversion:
  • If you want to support maximum testability. This approach allows you to inject concrete test classes into different layers of the design.

Consider using a message-based approach:
  • If you are implementing a Web application and defining the interface between the presentation layer and business layer.
  • If you have an application layer that must support multiple client types.
  • If you want to support interaction across physical and process boundaries.
  • If you want to formalize the interaction with a common structure.
  • If you want to interact with a stateless interface where state information is carried with the message.

When it comes to the interaction between the presentation layer of a Web application and the business logic layer, the recommendation is to use a message-based interface. This is normally accomplished using Windows Communication Foundation (WCF) or an abstract interface designed to support a common message type. The reason for using a message-based interface between the presentation layer and business layer is related to the fact that the business layer for a Web application does not maintain state between calls. In other words, each call between the presentation layer and business layer in a Web application represents a new context. By using a message-based interface, you can pass context information along with the request and provide a common model for exception and error handling in the presentation layer.

Check Point

Before you finish this step, you should be able to answer the following questions:
  • Do you need to support testability with abstract interfaces?
  • Does your interface need to support interaction across process and physical boundaries?
  • Does your business layer need to support multiple client types?
  • Will you use a message-based interface for interaction between the presentation and business layers of a web application?

Step 4 – Choose a 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.

Client-Server

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.
ClientServer.JPG

N-Tier

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.

NTier.JPG

3-Tier

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.

3Tier.JPG

4-Tier

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.

4Tier.JPG
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.

Consider 3-tier:
  • 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.

Consider 4-tier:
  • 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.

CheckPoint

Before you finish this step, you should be able to answer the following questions:
  • Are you developing a stand-alone client that just needs access to a database?
  • Are you developing a web application or service?
  • Do security requirements dictate that business logic cannot be deployed to a publicly exposed perimeter network?
  • Are you developing an application with multiple clients?

Step 5 – Choose Communication Protocols

When it comes to the physical protocols used for communication across layers or tiers in your design, the communication protocol you use plays a major role in the performance, security and reliability of the application. The choice of communication protocol is even more important when considering distributed deployment. When components are located on the same physical tier, you can often rely on direct communication between these components. However, if you deploy components and layers on physically separate servers and client machines - as is likely in most scenarios - you must consider how the components in these layers will communicate with each other efficiently and reliably.

The following guidelines will help you to choose the communication protocol:
  • Consider the deployment scenario which your application will support for choosing the communication mechanism.
  • Consider using message-based communication when crossing physical boundaries.
  • Consider using object-based communication when crossing logical boundaries.
  • Design coarse-grained interfaces to reduce round trips between the server and the client.

The two major areas where you will need to choose a communication protocol is when using services and interacting with the database.

Windows Communication Foundation (WCF) – supports four different protocols out of the box:
  • HTTP – used for services exposed publicly over the Internet.
  • TCP – used for services deployed within a network.
  • NamedPipes – used for services deployed on the same server as the consumer.
  • MessageQueue – used for services that are accessed through a Microsoft Message Queue implementation.

Database – supports many of the same protocols as WCF.
  • HTTP – used for a database exposed publicly over the Internet.
  • TCP – used for a database deployed within a network.
  • NamedPipes – used for a database on the same server as the consumer.

Consider using HTTP:
  • If you need to provide public access to your service or database over the Internet.
  • If you want to take advantage of Web Service Extensions (WS-*) for security that are supported by the HTTP protocol.

Consider using TCP:
  • If communication is limited to servers that exist on the same network.
  • If you are using a Virtual Private Network (VPN) for access to services or databases.
  • If you do not need to use Web Service Extensions for security.

Consider using NamedPipes:
  • If your service or database is deployed on the same server as the consumer.
  • If you do not need to use Web Service Extensions for security.

Consider using MessageQueue:
  • If you have a service that is accessed through a message-bus design using Microsoft Message Queuing.

Check Point

Before you finish this step, you should be able to answer the following questions:
  • What type of boundaries do you need to cross when accessing a service or database?
  • Are you using a message-bus design that uses Microsoft Message Queue?
  • Do you need to use Web Service Extensions?

Additional Resources

For more information on layered designs and deployment scenarios, see the following resources:

Last edited Dec 17, 2008 at 10:03 PM by prashantbansode, version 3

Comments

No comments yet.