1
votes

I am working on an asp.net project with oracle as backend. Initialy I developed this application using three layer architecture with UI as aspx pages, BLL and DAL as class library projects. I used static classes and methods in both BLL and DAL. DAL only consisted of single class with static methods of Select, Execute and ExecuteScalar which accepted queries forwarded by BLL classes.

DAL

private static string connString = ""

private static OracleConnection conn;

public static OracleConnection OpenConn()
{
  if (conn==null)
  {
    conn = new OracleConnection(connString);
  }

  if (conn.State != ConnectionState.Open)
  {
    conn.Open();
  }

  return conn;
}

public static DataTable Select(string query)
{
  DataTable dt = new DataTable();
  OracleDataAdapter da = new OracleDataAdapter(query, OpenConn());
  da.Fill(dt);
  return dt;
}

public static void Execute(string query)
{
  OracleCommand cmd = new OracleCommand(query, OpenConn());
  cmd.ExecuteNonQuery();
}

public static int ExecuteScaler(string query)
{
  OracleCommand cmd = new OracleCommand(query, OpenConn());
  int id = Convert.ToInt32(cmd.ExecuteScalar());
  return id;
}

The way this DAL is called by BLL classes is as below Employee BLL

public static DataTable GetEmployees(int facilityid)
    {
        DataTable dt = new DataTable();
        string q = string.Format("SELECT * FROM ..");
        dt = OraDAL.Select(q);
        return dt;
    }


    public static DataTable AddEmployee(string name, , int departmentid , int employeeType)
    {
        DataTable dt = new DataTable();
        string q = string.Format("INSERT INTO ...");
        dt = OraDAL.Select(q);
        return dt;
    }

Now I am refactoring the application. Same three layer architecture with BLL and DAL as class library projects with an additional class library project named Domain to contain domain classes which is referenced by all other projects. I use these classes for data transfer between layers. This time keeping DAL classes as static and BLL as normal classes. I have moved most of the functionality (queries) in to DAL. Now DAL has classes like EmployeesDAL, DepartmentsDAL along with the generic class that contains Select, Execute and ExecuteScalar static methods.

Employee.cs

public class Employee
{
    public int Id { get; set; }

    public string Name { get; set; }

    public Department department { get; set; }

    public EmployeeType employeeType { get; set; }
}

EmployeesDAL

public static List<Employee> GetEmployees(int departmentid)
    {
        if (departmentid > 0)
        {
            string query = string.Format("SELECT * FROM EMPLOYEES WHERE department='{0}')", departmentid);
            return GetCollection(OraDAL.Select(query));
        }

        return null;
    }

    public static Employee GetEmployee(int employeeid)
    {
        if (employeeid > 0)
        {
            string query = string.Format("SELECT * FROM PMS_EMPLOYEES WHERE Id='{0}'", employeeid);
            return GetSingle(OraDAL.Select(query));
        }

        throw new Exception("Employee id not valid");
    }

    public static int AddEmployee(Employee employee)
    {
        if (employee !=  null)
        {
            string query = string.Format("INSERT INTO PMS_EMPLOYEES (name, department, employee_type) VALUES ('{0}','{1}','{2}')", employee.Name, employee.department.Id, employee.employeeType.Id);
            return OraDAL.Execute(query);
        }

        throw new Exception("Values not valid");
    }

private static List<Employee> GetCollection(DataTable table)
    {
        List<Employee> employees = null;
        if (table != null)
        {
            if (table.Rows.Count > 0)
            {
                employees = new List<Employee>();
                foreach (DataRow row in table.Rows)
                {
                    employees.Add(ReadDataRow(row));
                }
            }
        }
        return employees;
    }

    private static Employee GetSingle(DataTable table)
    {
        if (table != null)
        {
            if (table.Rows.Count > 0)
            {
                DataRow row = table.Rows[0];
                return ReadDataRow(row);
            }
        }

        return null;
    }

    private static Employee ReadDataRow(DataRow row)
    {
        Employee employee = new Employee() 
        {
            Id=int.Parse(row["ID"].ToString()),
            Name=row["NAME"].ToString(),
            employeeType=EmployeeTypesDAL.GetEmployeeType(int.Parse(row["EMPLOYEE_TYPE"].ToString())),
            department=DepartmentsDAL.GetDepartment(int.Parse(row["DEPARTMENT"].ToString()))
        };

        return employee;
    }

EmployeesBLL.cs

 public class EmployeesBLL
{
    public List<Employee> GetEmployees(int departmentid)
    {
        if (departmentid > 0)
        {
            return EmployeesDAL.GetEmployees(departmentid);
        }

        return null;
    }

    public int AddEmployee(Employee employee)
    {
        if (employee != null)
        {
            return EmployeesDAL.AddEmployee(employee);
        }

        throw new Exception("Employee cannot be null");
    }

The post is getting longer but I want you to have a better idea of the situation. I am facing few issues with the new design and made me ask this question, is this the right approach to do things? Since the main goal is to avoid moving datatables back and forth among different layers. Though this new way of coding the logic is too time consuming but is it worth the effort. The business layer is getting too thin as it seems the only service they provide is to call similar methods from DAL, do I need BLL in first place? If yes what possible situations could be where a separate BLL is necessary.

Edit

One issues that I noted with the above design is the number of database calls. Too many calls since when an object is populated all child objects are also populated resulting in calls to database. For instance populating Employee object will result in populating department instance. In most cases I would only need department id rather than whole department information.

2
Your design is a nice sample of data-driven design, and yes - in it business logic is just a wrapper over crud operations. And that is really fine, if your application does only crud. By the way, if you're planning to put more responsibility to BL, think about O/RM approach, otherwise you'd spend too much time supporting your data access layer codebase.mikalai

2 Answers

1
votes

The purpose of the parts of 3-tier architecture are as follows:

1. UI

Allows the user to interact with your domain model

2. Domain Model/Middle Tier

Contain a translation of your particular domain or business process into code. Serves as a representation of your particular business problem or environment. This tier should contain all of the business rules and business objects for your application.

3. Data access layer

Allows your domain model objects to be persisted to and retrieved from a data store. A data access layer simply takes data from a persistent data store (i.e. a database) and turns it into domain model objects, and takes domain model objects and persists them to a data store.

Of these three areas, in my option the most important area by far is the Domain Model/Middle tier. This is the heart and soul of your application and is where the application actually solves the particular business problem and provides value to the orginazation that will be using your software. The purpose of the other two areas are simply to expose your domain model to users (the UI) or to allow it to be retrieved or stored (the data layer).

The implications of this are that you should strive to spend as much of your effort as possible working on the domain model/middle tier. This is where you solve the particular business problems of your organization and is where you can really add value to your company or customers.

In applying this general principles to your particular situation, I have the following comments/suggestions.

  1. You are manually creating your data access layer and hand-crafting SQL statements. In general, this is a very low value use of your time. There are a number of object-relation mapper (ORM) tools out there, such as nHibernate or entity framework that will take care of the low-level plumbing needed to load domain model objects from a data store and persist persist the objects back to the data store. I think you may want to investigate these. I think a solution using one of these ORM tools would be a better solution than either of the options you have suggested.

  2. Based on the code you posted, your domain model/middle tier is just a bag of properties, with no business logic or business rules. If this is truly the case, then that is what it should be. However, it would be a good use of your time to talk to your stakeholders and really make an effort to model their processes in the domain model, since this is where you can best add value to your organization.

I know this got a little long winded but hope it is helpful to you.

1
votes

Presentation Tier:

You already know this. It is your UI. It consists of view, view model and (controller / presenter). This presentation tier only has logic related to UI (positioning, css styling, etc). It does not know about implementation of BLL (just the interface), and does not know the DAL (storage) at all.

Business Tier

Where the logic of your business resides. Every select operation, calculation, validation, insert operation, deletion, update operation are belong here. It does not know about the UI, so it can works in ANY UI, either web/desktop/mobile application, with same busienss logic. It means in this tier, no obvious caching/session were used. Instead, you can wrap it in some interface. It does not know about the storage, meaning you need another layer to do the storage, and it is DAL.

Data Access Tier

This tier is simply doing CRUD operation with the storage (flat file, database, web service, xml, json, etc), and returning object model. That's all. It does not know the UI nor the business rule. The purpose of this tier is to make it easy for replacement. Say that you need to replace the storage from database to xml, this is the tier's job, leaving other layer untouched.

Answering your Question

Hope you already know the purpose of 3 tier.

Now assuming you are using 3 tier with BLL in your case:

  1. Any changes to Employee storage does not affect BLL and UI
  2. Any changes to business logic (new search parameter) does affect DAL
  3. Your BLL does not know the data access (connection / db context, etc), means it does not need to know the connection at all
  4. Additional obvious mapping at BLL

Now assuming you are using 2 tier without BLL in your case:

  1. Any changes to Employee storage does not affect UI (no BLL)
  2. Any changes to business logic (new search parameter) does affect DAL
  3. Your UI know the DAL. If you designed it as separated project, a reference is added and breaking the layering. This is opening chances to your UI to directly access other DAL.

Yes, it is rather useless to have BLL in your case, since you only get benefit to separate your UI and DAL. But that is the point of 3 tier isn't it?

That is, if you are using stored procedure select. What you really need is generic repository pattern in DAL, which will help you greatly during BLL access.