Skip to content

Layered Architecture

jbe2277 edited this page Aug 22, 2015 · 5 revisions

1. Introduction

This document describes a concrete example architecture for .NET / WPF Rich Client Applications. This architecture doesn’t claim to be the preferable solution in every scenario but you might find the one or other part described here useful for your own software systems.

2. Layered Architecture

Layering is a very useful tool in structuring object-oriented software systems. It helps to organize the types and namespaces into a large-scale structure. The layered architecture defines a few rules to ensure that the structuring is done right. One of the rules says that “lower” layers are low-level and general services, and the “higher” layers are more application specific. This way you are able to divide the software artifacts with related responsibilities together into discrete layers. Another rule says that “higher” layers call types of “lower” layers, but mustn’t vice versa. This rule helps to reduce the coupling and dependencies between the types and so increase the reuse potential, testability and clarity of the software system.

When a team agrees to use a layered architecture then it has to choose one of the hundreds hardly distinguishable layering schemes found in books about software design and architecture. When you are interested to dig deeper into the different layering schemes then you might start with the book Patterns of Enterprise Application Architectures [Fowler03]. I personally prefer the layering scheme shown in Larman’s book Applying UML and Patterns [Larman04]. Figure 1 shows this layering scheme which I have adapted to reflect the .NET / WPF platform.

Figure 1: Layers in a .NET / WPF based software system.
Figure 1: Layers in a .NET / WPF based software system.

3. Guidelines for a Layered Architecture

1. General vs. Application Specific Types
Separate the types into the different layers. Put the low-level and general service types into the “lower” layers, and the more application specific types into the “higher” layers. This rule is shown by the “more app specific” arrow in Figure 1.

2. Dependencies
The characteristic of a layered architecture is that “higher” layers call upon services of “lower” layers, but mustn’t vice versa. This way we avoid circular references between types of different layers and we decrease the number of dependencies. This guideline can be seen by the “dependency” arrow in Figure 1.

3. Applicability
A result of the first and second guideline is that “lower” layers are more reusable than “higher” layers. Figure 1 shows this via the width of the UML packages and the “With implies range of applicability” arrow.

4. Relaxed Layering
This is also known as layer skipping. Relaxed Layering means that layers are allowed to call layers deeper than the one directly below them. By example a view in the Presentation layer can bind a text box to a property of a business class from the Domain layer.

5. White-box reuse
A class is allowed to inherit from a class defined in a lower layer. The same is true for implementing interfaces. However, white-box reuse tightens the coupling between the layers and should be avoided whenever possible. One of the object-oriented design principles in the book Design Pattern [GHJV95] is “Favor object composition over class inheritance”.

6. Namespaces
The layer name might be a part of the namespace. A common naming scheme for namespaces is:

Schema: [Company].[Product].[Layer].[SubSystem]
Example: Microsoft.Word.Presentation.Ribbon, Microsoft.Outlook.Domain.AddressBook

Tip 1:
Avoid the name Application inside a namespace because the base class of App.xaml is also named Application. Therefore, I use the plural name Applications inside my namespaces to refer to the Application layer.

Tip 2:
Avoid assemblies that contain types from different layers. When every assembly is associated with just one layer then you gain the advantage that assembly references don’t allow cyclic dependencies. This way the compiler helps you to ensure that all the dependencies are from “higher” to “lower” layers. Furthermore, the internal access modifier restricts the access to the types inside the same layer. I believe that you should avoid accessing internal types or members over layer boundaries.

4. Presentation Layer

This layer is responsible for the look and feel of the application. It contains the WPF views, user controls, custom controls, resources, styles, templates, etc. Also ValueConverters are common in this layer especially when they convert data from lower layers into WPF objects (e.g. Convert an enum value into an ImageSource). Besides the WPF Framework you might use further technologies for reporting, audio output, speech interface, etc. The code which interacts with these technologies might also be a good candidate for the Presentation layer.

Common Issues

  1. Implementing business logic in the Presentation layer instead of the Domain layer.
  2. Implementing the UI workflow in in the Presentation layer instead of the Application layer.
    Example: The click event handler of the Options button creates and shows the Options window.
  3. Implement validation logic in the Presentation layer instead of the Domain layer. The Presentation layer is only responsible to trigger the validation but it is not the place for the validation logic itself. When the validation fails the Presentation layer has to show a user-friendly message.

Automated Testing
The Presentation layer is difficult to test because the own code is tightly coupled with the WPF Framework. Two common approaches for testing this layer are:

  • Automated UI testing with the UI Automation Framework which is well supported by WPF. With this Framework you are able to simulate user interaction and you can track the reaction of your application.
  • Writing unit tests for your Presentation layer code. In this case you should emulate the behavior of WPF to test your code in a realistic environment. This can be tricky because WPF uses the Dispatcher threading subsystem.

5. Application Layer

The Application layer is responsible for the application workflow. A common way to model a static workflow is done with Controller classes. In this case you might have an ApplicationController which is initialized during the start-up sequence. When a program function is triggered the ApplicationController might delegate the process of this function to a use case controller.

This layer has the same width as the Presentation layer in Figure 1. The width implies the range of applicability of the layer. Thus, the Application layer is not more reusable than the Presentation layer. This is because the layer is not completely independent of WPF and so it cannot be reused in other application types (e.g. Web Application). Common WPF types found in the Application layer are:

  • ICommand interface - Command Pattern
  • PropertyChangedEventManager class - Weak Events
  • Dispatcher class - Synchronization with the UI Thread

However, even if it’s not totally forbidden to use a type of the WPF Framework in this layer you should avoid using other types as the ones mentioned above.

Because the layer is weakly coupled with the WPF Framework you are not able to reuse this code with another Presentation technology (e.g. ASP .NET). I don’t believe that this is a real issue because the application workflow often depends on the Presentation technology. By example, desktop applications use mostly different workflows than web applications. In such a case we would have to write two different implementations of the Application layer anyway.

The motivation of this layer is to separate the UI from the workflow concerns. The introduction of the Application layer should improve the testability and maintainability of software systems.

Common Issues

  1. Implementing business logic in the Application layer instead of the Domain layer.
  2. WPF types are implemented or used within the Application layer although they belong to the Presentation layer (e.g. Window, UserControl, Control, Resources, Styles and Templates). This often reduces the testability of this layer.
  3. Forbidden namespace dependencies:
    • System.Windows.Controls
    • System.Windows.Data
    • System.Windows.Media
    • System.Media
    • System.Waf.Presentation
  4. Assembly references you shouldn’t find in this layer:
    • PresentationFramework
    • System.Drawing
    • System.Windows.Forms

The forbidden namespace dependencies can be defined in Visual Studio Layerdiagrams. This way the Build servers are able to validate these rules during the architecture validation.

Automated Testing
This layer should be designed in a way so that you are able to unit test all of the code. It shouldn’t contain unit testing barriers like the Presentation layer does. If unit testing cannot be done with less effort you might review the design of your Application layer.

6. Domain Layer

The domain layer is responsible for the business logic and only for the business logic. It is essential to separate the concerns here because in my experience the business logic is more than complex enough without mixing it with other aspects. By example, any UI specific code here would break the principles of the layered architecture.

Another reason why you should separate the concerns in this layer is shown by Martin Fowler: “Since the behavior of the business is subject to a lot of change, it’s important to be able to modify, build, and test this layer easily. As a result you’ll want the minimum of coupling from the Domain Model to other layers in the system. [Fowler03]”

Unfortunately, the separation of concerns isn’t that easy to follow with data access strategies. Ideally, a developer working in the domain layer shouldn’t care about how the data is stored in a database or a file. The separation of business logic and data access strategies is also known as Persistence Ignorance (PI) or Plain Old CLR Objects (POCO). Modern persistence frameworks try to support this concept but all of them come with some modeling limitations for the business objects. Therefore, you should decide which data access strategies you are going to use before starting to model the business rules.

Software systems mostly need some kind of validation to ensure that the business logic is working with correct data. These validation rules are defined by the business model and so the Domain layer is the right place to implement them. Just keep in mind that you should avoid to duplicate the validation code between the business objects.

Common Issues

  1. The validation logic code or the business logic code is duplicated in other layers. By example you have implemented a custom WPF ValidationRule which contains the same validation logic than the business class.
  2. You can’t unit test the business logic decoupled from the data source. By example you have to set up a database with predefined test data when you write unit tests for your business logic.
  3. Forbidden namespace dependencies:
    • System.Windows
    • System.Media
    • System.Waf.Presentation
    • System.Waf.Applications
  4. Assembly references you shouldn’t find in this layer:
    • PresentationFramework
    • PresentationCore
    • WindowsBase
    • System.Drawing
    • System.Windows.Forms

Automated Testing
The domain layer is the heart of your application. In this layer you have the code which is important to your customer. Your customer is seldom interested to get a polished fancy user interface but he is paying attention on how the software solves his problem domain.

That said I believe writing unit tests for your business logic is essential to guarantee a high quality software system. Unfortunately, some persistence frameworks make unit testing isolated from the data source very hard. This has a negative impact on time and effort to write all the unit tests for your business logic. I would consider this point when choosing a persistence framework for your application.

7. Business Infrastructure Layer

The Business Infrastructure layer contains reusable services which are domain specific. The difference to the Domain layer is that the types of this layer can be reused in other software systems whereas the Domain layer is designed for the system it is created for.

Examples

  • Currency Converter

Common Issues

  1. The forbidden namespace dependencies and the assembly references you shouldn’t find in this layer are the same as for the Domain layer.

8. Technical Services Layer

The Technical Services layer contains “high level” services which are independent of the business. These services are mostly part of a reusable library or a Framework.

Examples

  • Persistence Frameworks (e.g. ADO .NET Entity Framework)
  • Validation Frameworks (e.g. System.ComponentModel.DataAnnotations)

Common Issues

  1. The forbidden namespace dependencies and the assembly references you shouldn’t find in this layer are the same as for the Domain layer.

9. Foundation Layer

The Foundation layer provides “low level” technical services. The BCL (Base Class Library) of the .NET Framework would be a good candidate for this layer.

Foundation is the most depended-on layer because all layers can use types from this one. Therefore, it has to be more stable than the other layers. Stable means in this context that existing public members are not changed in signature and in their behavior.

Examples

  • Collection classes (e.g. List<T>)
  • Logging (e.g. TraceSource)

Common Issues

  1. The forbidden namespace dependencies and the assembly references you shouldn’t find in this layer are the same as for the Domain layer.

10. References

  • Fowler03. Fowler, M. 2003. Patterns of Enterprise Application Architecture. Addison Wesley.
  • GHJV95. Gamma, E., Helm, R. Johnson, R., Vlissides, J. 1995. Design Patterns. Elements of Reusable Object-Oriented Software. Addison Wesley.
  • Larman04. Larman, C. 2004. Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development, Third Edition. Addison Wesley.
  • Microsoft09. Application Architecture Guide 2.0. Designing Applications on the .NET Platform. Microsoft.