N-Tier Architecture Best Practices, Part 2: 3-Tier Architecture with interfaces and a Data Tier

Microsoft .NET

N-Tier Architecture Best Practices, Part 1: 2-Tier Architecture with just a Data Tier
N-Tier Architecture Best Practices, Part 2: 3-Tier Architecture with interfaces and a Data Tierthis article
N-Tier Architecture Best Practices, Part 3: DLinq / Linq to SQL
N-Tier Architecture Best Practices, Part 4: Entity Framework
N-Tier Architecture Best Practices, Part 5: Unity Framework

Download the Source Code for this Article
N-Tier Architecture (3-Tier)

In the previous article I covered 2-Tier Architecture with just a presentation layer and a data tier. In this article, I will show you how to expand this into a 3-tier architecture that will allow you to utilize your data tier with flexibility. You will need to download the source code from the previous article for this example, because I will be expanding upon it. You should already be very familiar with the code before proceeding.

Now let’s say you wanted to add a library to the project to handle all the rules of the business. This might be something like making sure that all employees in the company have valid pay rates and salaries. So we add a new Class Library project and call it NTier.BusinessRules along with a class called EmploymentValidation.

//-----------------------------------------------------------------------------
// <copyright file="EmploymentValidation.cs" company="DCOM Productions">
//     Copyright (c) DCOM Productions.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------

namespace NTier.BusinessRules {
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// Validates employees to ensure their profiles meet business rules and standards
    /// </summary>
    public static class EmploymentValidation {
        /// <summary>
        /// Validates the specified pay rate
        /// </summary>
        public static bool ValidatePayrate(float rate) {
            if (rate < 0f) {
                return false;
            }
            return true;
        }
    }
}

Now this is great, because in NTier.Data.DataTier we can validate the employee’s payrate in AddEmployee and UpdateEmployee. So let’s add a reference to NTier.BusinessRules from NTier.Data and add our validation. Note that I will use an elipse ( … ) to represent sections of code we are not changing to help condense this post.

Changes to NTier.Data.DataTier.cs

namespace NTier.Data {
    using System;
    using System.Collections.Generic;
    using System.Data.SqlServerCe;
    using NTier.Data.Objects;
    using NTier.BusinessRules;
    ...
}
public static bool AddEmployee(NTier.Data.Objects.Employee employee) {
    if (!EmploymentValidation.ValidatePayrate(employee.Payrate)) {
        return false;
    }
    ...
}
public static bool UpdateEmployee(NTier.Data.Objects.Employee employee) {
    if (!EmploymentValidation.ValidatePayrate(employee.Payrate)) {
        return false;
    }
    ...
}

Okay, great; if we try to add or update an employee that has a payrate below 0, it will fail and that is what we want. Now this seems great, we just added validation to our project with ease. Now, what if your employee object actually has 50+ properties that need to be validated? Often times you want to keep your object itself simple. This will help you maintain it in the future, so the first thought is to pass the object itself to our validation library. Wait, we can’t!

NTier.Data references NTier.BusinessRules, therefore NTier.BusinessRules can never reference NTier.Data as this would cause a circular dependency. This is where 3-tier architecture using interfaces comes into play. We don’t need to pass the object itself, we can pass a contract that defines that object and stores the valuable information we need to validate. We don’t need anything else, but we need to add another new project called NTier.Common.Interfaces and our IEmployee interface to represent our object.

//-----------------------------------------------------------------------------
// <copyright file="IEmployee.cs" company="DCOM Productions">
//     Copyright (c) DCOM Productions.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------

namespace NTier.Common.Interfaces {
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// Defines the base properties for an Employee
    /// </summary>
    public interface IEmployee {
        int ID { get; }
        string Email { get; set; }
        string FirstName { get; set; }
        string LastName { get; set; }
        float Payrate { get; set; }
        string Title { get; set; }
    }
}

Now first what we want to do is add a reference to NTier.Common.Interfaces from NTier.Data and derive Employee from IEmployee. Also don’t forget that because NTier.Presentation uses NTier.Data, it must also use NTier.Common.Interfaces, so we add that as a reference as well. Add your references, and make the following change to Employee.cs.

public class Employee : NTier.Common.Interfaces.IEmployee {
    ...
}

Now to resolve the circular dependency we are going to reference NTier.Common.Interfaces from NTier.BusinessRules. Add your reference, then make the following changes to EmploymentValidation.

namespace NTier.BusinessRules {
    ...
    using NTier.Common.Interfaces;

    ...
    public static class EmploymentValidation {
        ...

        /// <summary>
        /// Validates the specified employee
        /// </summary>
        public static bool ValidateEmployee(IEmployee employee) {
            if (string.IsNullOrEmpty(employee.FirstName))
                return false;
            if (string.IsNullOrEmpty(employee.LastName))
                return false;
            if (string.IsNullOrEmpty(employee.Title))
                return false;
            if (string.IsNullOrEmpty(employee.Email))
                return false;
            if (!ValidatePayrate(employee.Payrate))
                return false;
            return true;
        }
    }
}

Now notice that I left in the ValidatePayrate() method. This is because often times you are already using this in various portions of your software, and removing or changing this would be a breaking change that could break the software. So we’ll leave that in. But now you can see that we can pass in an interface of IEmployee and validate everything we need. But first, we need to go back and update our AddEmployee() and UpdateEmployee() methods in our data tier.

public static bool AddEmployee(NTier.Data.Objects.Employee employee) {
    if (!EmploymentValidation.ValidateEmployee(employee)) {
        return false;
    }
    ...
}
public static bool UpdateEmployee(NTier.Data.Objects.Employee employee) {
    if (!EmploymentValidation.ValidateEmployee(employee)) {
        return false;
    }
    ...
}

And, we’re done. We can pass around our IEmployee interface because it is shared among all the core libraries, where before we could not have passed around Employee due to circular dependencies. This is what 3-tier architecture with interfaces provides, flexibility and loose coupling between your libraries. Technically it is still pretty tightly coupled, but much more better than a 2-tier architecture.

6 Comments

  1. Pingback: N-Tier Architecture Best Practices, Part 1: 2-Tier Architecture with just a Data - Blog - danderson.io

  2. samir

    Hi,
    I’m not able to download code for this article. getting following error-

    HTTP Error 404.0 – Not Found

    Regards,
    Samir

    Reply
  3. Akber

    Hi, When you are publishing the LINQ, Entity Framework part 3 -5. I am desperately would like to learn it.

    Reply
  4. Jebb Burditt

    Great article, will come back when there is more.

    Reply
  5. Tamir

    I really enjoyed reading this, where are the rest of the articles for this series?

    Thanks!

    Reply
  6. Pingback: N-Tier Architecture Best Practices, Part 1: 2-Tier Architecture with just a Data | David Anderson

Leave a Comment

Your email address will not be published.