Tag Archive: architecture

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.

N-Tier Architecture Best Practices, Part 1: 2-Tier Architecture with just a Data

Microsoft .NET

Before I start off on my lengthy post, this article is what sparked my interest. With that linked for you, this will be split into a 5 part blog series each part covering a specific type of n-tier architecture. If there is not a link to the article it has not been published yet.

N-Tier Architecture Best Practices, Part 1: 2-Tier Architecture with just a Data Tierthis article
N-Tier Architecture Best Practices, Part 2: 3-Tier Architecture with interfaces and a Data Tier
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 (2-Tier)

In most projects you will have to communicate with other objects or a data tier (database engine, file system, or other data source), and passing your objects around can make for some complicated scenarios. One common rut that many developers find themselves in is circular-dependencies.

What is N-Tier Architecture?
N-Tier Archiecture is a term that refers to the number of assemblies, modules, or services that make up a system that allows its different parts to communicate with one another. An example would be a simple system that has some business objects and a database. You may have a module that handles the communication with the data tier, and also stores your business objects, and then maybe a second module that actually handles the processing of the objects such as input and change. This would be a 2-Tier architecture. A 3-Tier architecture would be something like having a module that stores interfaces that defines your business objects, another module that actually implements the business objects, and then a third module that again handles the input and change. N-Tier architecture can get vastly complex, especially in software applications like video games. Imagine a game engine where you have modules that must communicate with one another for handling rendering, player locations, particle effects, game data, network information. You can easily get into a complex system.

What I will cover

  • 2-Tier with just a data tier
  • 3-Tier with common interfaces, and a data tier
  • Briefly explain DLinq (Linq to Sql)
  • Briefly explain Entity Framework (Linq to Entities)
  • Briefly explain Unity Framework (Data Injection Framework)

Technologies we will be using for this article

  • C# 4.0
  • .NET 3.5
  • SQL Server CE

2-Tier architecture with a Data Tier Project Structure (image to the left)
We are going to create a solution with two projects. The first, ‘NTier.Data’ will act as the Data Tier. Thie purpose of this is to act as the dependency for all other aspects of the software. It will store the database objects, as well as the class that handles communication between the objects and the database. The second will be ‘NTier.Presentation’, which acts as the executable that is actually used by the client as the UI, and in our case is a very simple Console Application.

The database
The database is just a SqlCe database with an Employees table. Our employee object will match the database schema perfectly in terms of its properties (ie. FirstName, LastName, Email, etc).

The code
There are only three class files, here is the code of all three.

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

namespace NTier.Data.Objects {
    using System;

    /// <summary>
    /// Represents a Employee in the data tier
    /// </summary>
    public class Employee {
        /// <summary>
        /// Gets the Employee's ID
        /// </summary>
        public int ID {
            get;
            internal set;
        }

        /// <summary>
        /// Gets or sets the Employee's email address
        /// </summary>
        public string Email {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Employee's first name
        /// </summary>
        public string FirstName {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Employee's last name
        /// </summary>
        public string LastName {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Employee's payrate
        /// </summary>
        public float Payrate {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Employee's title
        /// </summary>
        public string Title {
            get;
            set;
        }

        /// <summary>
        /// Overrides base.ToString()
        /// </summary>
        public override string ToString() {
            return string.Format("{0} {1}, {2}", FirstName, LastName, Title);
        }
    }
}
//-----------------------------------------------------------------------------
// <copyright file="DataTier.cs" company="DCOM Productions">
//     Copyright (c) DCOM Productions.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------

namespace NTier.Data {
    using System;
    using System.Collections.Generic;
    using System.Data.SqlServerCe;
    using NTier.Data.Objects;

    /// <summary>
    /// Interactive class between business logic and the data tier
    /// </summary>
    public static class DataTier {

        private static string m_ConnectionString = @"Data Source=.\Northwind.sdf";
        /// <summary>
        /// Gets the connection string for SQL Server CE
        /// </summary>
        public static string ConnectionString {
            get {
                return m_ConnectionString;
            }
        }

        /// <summary>
        /// Adds the specified employee to the data tier
        /// </summary>
        public static bool AddEmployee(NTier.Data.Objects.Employee employee) {
            using (SqlCeConnection connection = new SqlCeConnection(ConnectionString))
            using (SqlCeCommand command = new SqlCeCommand(Properties.Resources.InsertCommandText, connection)) {
                command.Parameters.AddWithValue("@Email", employee.Email);
                command.Parameters.AddWithValue("@FirstName", employee.FirstName);
                command.Parameters.AddWithValue("@LastName", employee.LastName);
                command.Parameters.AddWithValue("@Payrate", employee.Payrate);
                command.Parameters.AddWithValue("@Title", employee.Title);
                try {
                    connection.Open();
                    return command.ExecuteNonQuery() == 1;
                }
                catch (System.Data.SqlServerCe.SqlCeException) {
                    return false;
                }
            }
        }

        /// <summary>
        /// Returns a collection of all the employee's in the data tier
        /// </summary>
        public static IEnumerable<NTier.Data.Objects.Employee> GetEmployees() {
            using (SqlCeConnection connection = new SqlCeConnection(ConnectionString))
            using (SqlCeCommand command = new SqlCeCommand(Properties.Resources.SelectCommandText, connection)) {
                try {
                    connection.Open();
                }
                catch (System.Data.SqlServerCe.SqlCeException) {
                    yield break;
                }
                using (SqlCeDataReader reader = command.ExecuteReader()) {
                    while (reader.Read()) {
                        Employee employee = new Employee();
                        employee.ID = (int)reader["ID"];
                        employee.Email = (string)reader["Email"];
                        employee.FirstName = (string)reader["FirstName"];
                        employee.LastName = (string)reader["LastName"];
                        employee.Payrate = (float)(double)reader["Payrate"];
                        employee.Title = (string)reader["Title"];
                        yield return employee;
                    }
                }
            }
        }

        /// <summary>
        /// Removes the specified employee from the data tier
        /// </summary>
        public static bool RemoveEmployee(NTier.Data.Objects.Employee employee) {
            using (SqlCeConnection connection = new SqlCeConnection(ConnectionString))
            using (SqlCeCommand command = new SqlCeCommand(Properties.Resources.DeleteCommandText, connection)) {
                command.Parameters.AddWithValue("@ID", employee.ID);
                try {
                    connection.Open();
                    return command.ExecuteNonQuery() == 1;
                }
                catch (System.Data.SqlServerCe.SqlCeException) {
                    return false;
                }
            }
        }

        /// <summary>
        /// Updates the specified employee's information in the data tier
        /// </summary>
        public static bool UpdateEmployee(NTier.Data.Objects.Employee employee) {
            using (SqlCeConnection connection = new SqlCeConnection(ConnectionString))
            using (SqlCeCommand command = new SqlCeCommand(Properties.Resources.UpdateCommandText, connection)) {
                command.Parameters.AddWithValue("@Email", employee.Email);
                command.Parameters.AddWithValue("@FirstName", employee.FirstName);
                command.Parameters.AddWithValue("@LastName", employee.LastName);
                command.Parameters.AddWithValue("@Payrate", employee.Payrate);
                command.Parameters.AddWithValue("@Title", employee.Title);
                command.Parameters.AddWithValue("@ID", employee.ID);
                try {
                    connection.Open();
                    return command.ExecuteNonQuery() == 1;
                }
                catch (System.Data.SqlServerCe.SqlCeException) {
                    return false;
                }
            }
        }
    }
}
//-----------------------------------------------------------------------------
// <copyright file="Program.cs" company="DCOM Productions">
//     Copyright (c) DCOM Productions.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------

namespace NTier.Presentation {
    using System;
    using NTier.Data.Objects;
    using NTier.Data;
    using System.Collections.Generic;

    internal static class Program {
        /// <summary>
        /// Entry Point
        /// </summary>
        public static void Main() {
            Employee employee = new Employee();
            employee.Email = "danderson@dcomproductions.com";
            employee.FirstName = "David";
            employee.LastName = "Anderson";
            employee.Payrate = 35F;
            employee.Title = "President";
            DataTier.AddEmployee(employee);
            foreach (Employee item in DataTier.GetEmployees()) {
                Console.WriteLine(item.ToString());
                Console.ReadKey(true);
            }
            Console.Write("Press any key to exit...");
            Console.ReadKey(true);
        }
    }
}

Something to note is lines 33, 54, 81, 98 of Employee.cs. I saved the query texts in the project’s resource file to keep the code tidy.

Now you can write Program.cs to act however you want, and play with the code. In my example all I am doing is adding an employee to the table with my name, then enumerating the employees and listing them to the console window. Since each time you run my example it adds an employee with the same information and never removes it, you will have x(f) employees where f is the number of times the application has been launched, in the database with the same information.

What advantage does 2-Tier architecture serve?
Alright, in this 2-tier architecture, the advantage is its simple. That’s all there is to it. For each data tier object (ie. something that would need to be committed into a data source), you can simply create the object with its properties, and write the methods in your data tier class to do the work. The simplicity of being able to just pass your object is what makes it desireable. Also note that you do not have any complex dependencies between assemblies, thus you never have to worry about a circular dependency, and its extremely easy to note where everything is at in your project.

In large projects, your Objects folder would get rather large with data tier objects. Which is fine. The disadvantage of a 2-tier architecture is generally as your application becomes exceedingly complex, the simple structure of the project no longer becomes viable because its just not that flexible. Further down the road if you wanted to refactor your objects out of the data tier assembly into a 3-tier architecture, your data tier breaks because you can no longer pass the reference of the object. Thus bringing us into the next part of the blog series, 3-tier architecture.

When to use 2-Tier Architecture
When your requirements are simple and you have a minimal number of assemblies that must reference your business objects. More so, it is actually easier to explain when not to use 2-tier architecture. I’ve compiled a short list of the most common scenarios you should not use 2-tier architecture for.

  • An assembly needs to reference your business objects, but not have access to the data tier
  • You want to extend your business object with additional functionality, but not expose those new features to the data tier

Those are actually the only two reasons that come to mind immediately. If you have some other reasons you can think of that are good, throw a comment down and I will add it to the list. At any rate, the point is that 2-tier is easy to implement, and pretty easy to manage with little effort. The huge downside is once it becomes large, it will be hard to refactor later on. The next blog in the series will be on 3-tier architecture which I will hope to start writing by Monday.