locked
Three-tier architecture question RRS feed

  • Question

  • User661555364 posted

    I am working on a legacy system and need to implement a three-tier architecture (presentation/business logic and data access).

    The project uses ADO.NET and all the SQL statements are held in the data access layer however I am not sure what to do regarding class design and in particular the best way to access these classes from each tier.

    For example, I read the 'users' database table in data access tier and create a 'user' object using the 'users' class. Do I then pass this object back to the business logic layer? It also could be useful in the presentation layer - so do I pass back the object again?

    Should I be creating some type of 'global class' that can be accessed from each tier or should I be passing back something else from data access > business > presentation tier?

    Many thanks

    Thursday, January 23, 2020 3:55 PM

Answers

  • User475983607 posted

    The data interface between layers is up to you to design.  You can certainly create a global data model that is shared across all layers but that usually only works well when the data is very simple like a lookup table to populate a dropdown list.  

    Usually you end up with a data interface for each layer which creates a clear separation.  Often the business and data layer have similar interfaces while the UI is driven by your customers and can be anything.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, January 23, 2020 4:05 PM
  • User1120430333 posted

    The project uses ADO.NET and all the SQL statements are held in the data access layer however I am not sure what to do regarding class design and in particular the best way to access these classes from each tier.

    You can use the DAO pattern in the DAL. Using the DAO pattern makes it easy to do low level CRUD operations with the database.

    https://en.wikipedia.org/wiki/Data_access_object

    https://javarevisited.blogspot.com/2013/01/data-access-object-dao-design-pattern-java-tutorial-example.html

    https://www.tutorialspoint.com/design_pattern/data_access_object_pattern.htm

    You'll want to implement an Interface on each class in the BLL and the DAL, becuase that promotes loose coupling. The DAO pattern already uses an Interface on each DAO classes.

    https://www.c-sharpcorner.com/blogs/understanding-interfaces-via-loose-coupling-and-tight-coupling

    You'll also want to implement the 'new is glue' principle and  use dependency injection for classes in the BLL and DA. You can use an IoC container such as Unity or others.

    https://ardalis.com/new-is-glue

    https://www.tutorialsteacher.com/ioc

    For example, I read the 'users' database table in data access tier and create a 'user' object using the 'users' class. Do I then pass this object back to the business logic layer? It also could be useful in the presentation layer - so do I pass back the object again?

    Should I be creating some type of 'global class' that can be accessed from each tier or should I be passing back something else from data access > business > presentation tier?

    A DTO passes through the tiers, and each tier woks/acts upon a DTO or DTO(s) accordingly.

    https://www.codeproject.com/Articles/1050468/Data-Transfer-Object-Design-Pattern-in-Csharp

    All the DTO(s) are kept in a classlib project like the Entities classlib project, you can name the project anything you want, but all projects have project reference to Entities and know what the DTO(s) are about.

    Here is  an example of the DAL using the DAO and DTO pattern. I am using EF, but don't have to use any ORM and just use straight up ADO.NET, diabase command objects, parametrized T-SQL.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Entities;
    
    namespace DAL
    {
        public interface IDaoProject
        {
            DtoProject GetProjectById(int id);
            List<DtoProject> GetProjectsByUserId(string userid);
            void CreateProject(DtoProject dto);
            void UpdateProject(DtoProject dto);
            void DeleteProject(int id);
        }
    }
    
    ==================================================================
    
    using System.Collections.Generic;
    using System.Linq;
    using System.Transactions;
    using DAL.Models.DB;
    using Entities;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Options;
    
    namespace DAL
    {
        public class DaoProject :IDaoProject
        {
            private readonly IOptions<ConnectionStrings> _options;
            
            public DaoProject(IOptions<ConnectionStrings> options)
            {
                _options = options;
            }
    
            public DtoProject GetProjectById(int id)
            {
                var dto = new DtoProject();
    
                using (var context = new ProjectManagementContext(_options))
                {
                    var project = (context.Projects.Where(a => a.ProjectId == id)).SingleOrDefault();
    
                    if (project == null) return dto;
                    dto.ProjectId = project.ProjectId;
                    dto.ClientName = project.ClientName;
                    dto.ProjectName = project.ProjectName;
                    dto.Technology = project.Technology;
                    dto.ProjectType = project.ProjectType;
                    dto.UserId = project.UserId;
                    dto.StartDate = project.StartDate;
                    dto.EndDate = project.EndDate;
                    dto.Cost = project.Cost;
                }
    
                return dto;
            }
    
            public List<DtoProject> GetProjectsByUserId(string userid)
            {
                var dtos = new List<DtoProject>();
    
                using (var context = new ProjectManagementContext(_options))
                {
                    
                    dtos = (from a in context.Projects.Where(a => a.UserId.Contains(userid))
                        select new DtoProject
                        {
                            ProjectId = a.ProjectId,
                            ClientName = a.ClientName,
                            ProjectName = a.ProjectName,
                            Technology = a.Technology,
                            ProjectType = a.ProjectType,
                            UserId = a.UserId,
                            StartDate = a.StartDate,
                            EndDate = a.EndDate,
                            Cost = a.Cost
                        }).ToList();
                }
    
                return dtos;
            }
    
            public void CreateProject(DtoProject dto)
            {
                using (var context = new ProjectManagementContext(_options))
                {
                    var project = new Projects
                    {
                        ClientName = dto.ClientName,
                        ProjectName = dto.ProjectName,
                        Technology = dto.Technology,
                        ProjectType = dto.ProjectType,
                        UserId = dto.UserId,
                        StartDate = dto.StartDate,
                        EndDate = dto.EndDate,
                        Cost = dto.Cost
                    };
    
                    context.Projects.Add(project);
                    context.SaveChanges();
               }
            }
    
            public void UpdateProject(DtoProject dto)
            {
                var project = new Projects();
                
                using (var context = new ProjectManagementContext(_options))
                {
                   project = (context.Projects.Where(a => a.ProjectId == dto.ProjectId)).SingleOrDefault();
                }
    
                if (project != null)
                {
                    project.ClientName = dto.ClientName;
                    project.ProjectName = dto.ProjectName;
                    project.Technology = dto.Technology;
                    project.ProjectType = dto.ProjectType;
                    project.UserId = dto.UserId;
                    project.StartDate = dto.StartDate;
                    project.EndDate = dto.EndDate;
                    project.Cost = dto.Cost;
                }
    
                using (var dbcontext = new ProjectManagementContext(_options))
                {
                    if (project == null) return;
                    dbcontext.Entry(project).State = EntityState.Modified;
                    dbcontext.SaveChanges();
                }
            }
    
            public void DeleteProject(int id)
            {
                Projects project;
    
                using (var context = new ProjectManagementContext(_options))
                {
                   project = (context.Projects.Where(a => a.ProjectId == id)).SingleOrDefault();
                }
    
                if (project == null) return;
    
                using (var newContext = new ProjectManagementContext(_options))
                {
                   
                    var tasks = new DaoTask(_options).GetTasksByProjectId(project.ProjectId);
                    using (TransactionScope scope = new TransactionScope())
                    {
                        foreach (var task in tasks)
                        {
                            new DaoTask(_options).DeleteTask(task.TaskId);
                        }
    
                        newContext.Entry(project).State = EntityState.Deleted;
                        newContext.SaveChanges();
    
                        scope.Complete();
                    }
                }
            }
        }
    }
    
    namespace Entities
    {
        public class DtoProject
        {
            public int ProjectId { get; set; }
            public string ClientName { get; set; }
            public string ProjectName { get; set; }
            public string Technology { get; set; }
            public string ProjectType { get; set; }
            public string UserId { get; set; }
            public DateTime StartDate { get; set; }
            public DateTime EndDate { get; set; }
            public decimal Cost { get; set; }
        }
    }
    

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Saturday, January 25, 2020 5:08 PM

All replies

  • User475983607 posted

    The data interface between layers is up to you to design.  You can certainly create a global data model that is shared across all layers but that usually only works well when the data is very simple like a lookup table to populate a dropdown list.  

    Usually you end up with a data interface for each layer which creates a clear separation.  Often the business and data layer have similar interfaces while the UI is driven by your customers and can be anything.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, January 23, 2020 4:05 PM
  • User661555364 posted

    Quite reassuring to know that I'm not completely wrong in what I was thinking. I assumed that each tier was completely cut-off from the other but I just couldn't work out how to pass common structures around.

    Do you know of any tutorials or documentation on implementing this in the correct way?

    Thursday, January 23, 2020 4:10 PM
  • User475983607 posted

    DevGuyUk

    Do you know of any tutorials or documentation on implementing this in the correct way?

    A good test is testing.  The design is good of you can test each layer individually by mocking adjacent layers.

    Thursday, January 23, 2020 4:20 PM
  • User-719153870 posted

    Hi DevGuyUk,

    I just couldn't work out how to pass common structures around.

    A complete Three-Tier Architecture example can explain this well. Usually, you can create DAO to pass structure around.

    Best Regard,

    Yang Shen

    Friday, January 24, 2020 6:37 AM
  • User-821857111 posted

    You'll need some kind of mapping layer that converts the data returned by the data layer into some kind of object that you can work with in .NET. Dapper is a pretty good option. As to the type of object that you return, that depends. In a relatively simple application, I see no reason why you can't pass business objects (Product, Category etc) directly to the presentation layer (via a business logic/service layer). If you have fairly views in your UI, you might return custom classes that hold all the data for the view. Some people refer to these as view models. Some call then DAOs. 

    Friday, January 24, 2020 8:02 AM
  • User1120430333 posted

    The project uses ADO.NET and all the SQL statements are held in the data access layer however I am not sure what to do regarding class design and in particular the best way to access these classes from each tier.

    You can use the DAO pattern in the DAL. Using the DAO pattern makes it easy to do low level CRUD operations with the database.

    https://en.wikipedia.org/wiki/Data_access_object

    https://javarevisited.blogspot.com/2013/01/data-access-object-dao-design-pattern-java-tutorial-example.html

    https://www.tutorialspoint.com/design_pattern/data_access_object_pattern.htm

    You'll want to implement an Interface on each class in the BLL and the DAL, becuase that promotes loose coupling. The DAO pattern already uses an Interface on each DAO classes.

    https://www.c-sharpcorner.com/blogs/understanding-interfaces-via-loose-coupling-and-tight-coupling

    You'll also want to implement the 'new is glue' principle and  use dependency injection for classes in the BLL and DA. You can use an IoC container such as Unity or others.

    https://ardalis.com/new-is-glue

    https://www.tutorialsteacher.com/ioc

    For example, I read the 'users' database table in data access tier and create a 'user' object using the 'users' class. Do I then pass this object back to the business logic layer? It also could be useful in the presentation layer - so do I pass back the object again?

    Should I be creating some type of 'global class' that can be accessed from each tier or should I be passing back something else from data access > business > presentation tier?

    A DTO passes through the tiers, and each tier woks/acts upon a DTO or DTO(s) accordingly.

    https://www.codeproject.com/Articles/1050468/Data-Transfer-Object-Design-Pattern-in-Csharp

    All the DTO(s) are kept in a classlib project like the Entities classlib project, you can name the project anything you want, but all projects have project reference to Entities and know what the DTO(s) are about.

    Here is  an example of the DAL using the DAO and DTO pattern. I am using EF, but don't have to use any ORM and just use straight up ADO.NET, diabase command objects, parametrized T-SQL.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Entities;
    
    namespace DAL
    {
        public interface IDaoProject
        {
            DtoProject GetProjectById(int id);
            List<DtoProject> GetProjectsByUserId(string userid);
            void CreateProject(DtoProject dto);
            void UpdateProject(DtoProject dto);
            void DeleteProject(int id);
        }
    }
    
    ==================================================================
    
    using System.Collections.Generic;
    using System.Linq;
    using System.Transactions;
    using DAL.Models.DB;
    using Entities;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Options;
    
    namespace DAL
    {
        public class DaoProject :IDaoProject
        {
            private readonly IOptions<ConnectionStrings> _options;
            
            public DaoProject(IOptions<ConnectionStrings> options)
            {
                _options = options;
            }
    
            public DtoProject GetProjectById(int id)
            {
                var dto = new DtoProject();
    
                using (var context = new ProjectManagementContext(_options))
                {
                    var project = (context.Projects.Where(a => a.ProjectId == id)).SingleOrDefault();
    
                    if (project == null) return dto;
                    dto.ProjectId = project.ProjectId;
                    dto.ClientName = project.ClientName;
                    dto.ProjectName = project.ProjectName;
                    dto.Technology = project.Technology;
                    dto.ProjectType = project.ProjectType;
                    dto.UserId = project.UserId;
                    dto.StartDate = project.StartDate;
                    dto.EndDate = project.EndDate;
                    dto.Cost = project.Cost;
                }
    
                return dto;
            }
    
            public List<DtoProject> GetProjectsByUserId(string userid)
            {
                var dtos = new List<DtoProject>();
    
                using (var context = new ProjectManagementContext(_options))
                {
                    
                    dtos = (from a in context.Projects.Where(a => a.UserId.Contains(userid))
                        select new DtoProject
                        {
                            ProjectId = a.ProjectId,
                            ClientName = a.ClientName,
                            ProjectName = a.ProjectName,
                            Technology = a.Technology,
                            ProjectType = a.ProjectType,
                            UserId = a.UserId,
                            StartDate = a.StartDate,
                            EndDate = a.EndDate,
                            Cost = a.Cost
                        }).ToList();
                }
    
                return dtos;
            }
    
            public void CreateProject(DtoProject dto)
            {
                using (var context = new ProjectManagementContext(_options))
                {
                    var project = new Projects
                    {
                        ClientName = dto.ClientName,
                        ProjectName = dto.ProjectName,
                        Technology = dto.Technology,
                        ProjectType = dto.ProjectType,
                        UserId = dto.UserId,
                        StartDate = dto.StartDate,
                        EndDate = dto.EndDate,
                        Cost = dto.Cost
                    };
    
                    context.Projects.Add(project);
                    context.SaveChanges();
               }
            }
    
            public void UpdateProject(DtoProject dto)
            {
                var project = new Projects();
                
                using (var context = new ProjectManagementContext(_options))
                {
                   project = (context.Projects.Where(a => a.ProjectId == dto.ProjectId)).SingleOrDefault();
                }
    
                if (project != null)
                {
                    project.ClientName = dto.ClientName;
                    project.ProjectName = dto.ProjectName;
                    project.Technology = dto.Technology;
                    project.ProjectType = dto.ProjectType;
                    project.UserId = dto.UserId;
                    project.StartDate = dto.StartDate;
                    project.EndDate = dto.EndDate;
                    project.Cost = dto.Cost;
                }
    
                using (var dbcontext = new ProjectManagementContext(_options))
                {
                    if (project == null) return;
                    dbcontext.Entry(project).State = EntityState.Modified;
                    dbcontext.SaveChanges();
                }
            }
    
            public void DeleteProject(int id)
            {
                Projects project;
    
                using (var context = new ProjectManagementContext(_options))
                {
                   project = (context.Projects.Where(a => a.ProjectId == id)).SingleOrDefault();
                }
    
                if (project == null) return;
    
                using (var newContext = new ProjectManagementContext(_options))
                {
                   
                    var tasks = new DaoTask(_options).GetTasksByProjectId(project.ProjectId);
                    using (TransactionScope scope = new TransactionScope())
                    {
                        foreach (var task in tasks)
                        {
                            new DaoTask(_options).DeleteTask(task.TaskId);
                        }
    
                        newContext.Entry(project).State = EntityState.Deleted;
                        newContext.SaveChanges();
    
                        scope.Complete();
                    }
                }
            }
        }
    }
    
    namespace Entities
    {
        public class DtoProject
        {
            public int ProjectId { get; set; }
            public string ClientName { get; set; }
            public string ProjectName { get; set; }
            public string Technology { get; set; }
            public string ProjectType { get; set; }
            public string UserId { get; set; }
            public DateTime StartDate { get; set; }
            public DateTime EndDate { get; set; }
            public decimal Cost { get; set; }
        }
    }
    

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Saturday, January 25, 2020 5:08 PM