views:

546

answers:

5

I am working hard to standardize one single way of Layered/n-Tiered design of my all applications.

I am trying to make all my applications 5 tiered.

Code:


| UI |

|

| Business Object |

|

| OR-Mapper |

|

| Data Access |

|

| RDBMS |

Suppose I am developing an application with a log-in/log-out capability for users. I am creating 4 projects under a VS2005 solution. Each project is for one of the upper 4 layers. I am designing my Business Object class as follows:-

public class User
{
    private string _username;
    public string Username
    {
        get { return _username; }
        set { _username = value; }
    }

    private string _password;
    public string Password
    {
        get { return _password; }
        set { _password = value; }
    }

    public User()
    {
    }

    public bool LogIn(String username, String password)
    {
        bool success = false;

        if (UserMapper.UsernameExists(username))
        {
            success = UserMapper.UsernamePasswordExists(username, password);
        }
        else
        {
            //do nothing
        }

        return success;
    }

    public bool LogOut()
    {
           bool success;
        //----some logic
           return success;
    }

    public static User GetUserByUsername(string username)
    {
        return UserMapper.GetUserByUsername(username);
    }

    public static UserCollection GetByUserTypeCode(string code)
    {
        return UserMapper.GetByUserTypeCode(code);
    }
}

This is how I am giving my objects some functionality that matches the real-world scenario. Here GetByUsername() and GetByUserTypeCode() are getter functions. These functions does't match a real-world logic. Coz, in real-world, a User never "Gets by Username" or "Gets by UserTypeCode". So these functions are kept static.

My class for O-R Mapper layer is as follows:-

public static class UserMapper
{
    public static bool UsernameExists(String username)
    {
        bool exists = false;

        if (UserDA.CountUsername(username) == 1)
        {
            exists = true;
        }

        return exists;
    }

    public static bool UsernamePasswordExists(String username, String password)
    {
        bool exists = false;

        if (UserDA.CountUsernameAndPassword(username, password) == 1)
        {
            exists = true;
        }

        return exists;
    }
}

And finally, the DA class is as follows:-

public static class UserDA
{
    public static int CountUsername(string username)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }  

    public static int CountUsernameAndPassword(string username, string password)
    {
        int count = 0;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name AND Pass_word = @Pass_word";
                command.Parameters.AddWithValue("@User_name", username);
                command.Parameters.AddWithValue("@Pass_word", password);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = 0;
            }
        }

        return count;
    }

    public static int InsertUser(params object[] objects)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"INSERT INTO User(ID, User_name, Pass_word, RegDate, UserTypeCode, ActualCodeOrRoll) 
                                                            VALUES(@ID, @User_name, @Pass_word, @RegDate, @UserTypeCode, @ActualCodeOrRoll)";
                command.Parameters.AddWithValue("@ID", objects[0]);
                command.Parameters.AddWithValue("@User_name", objects[1]);
                command.Parameters.AddWithValue("@Pass_word", objects[2]);
                command.Parameters.AddWithValue("@RegDate", objects[3]);
                command.Parameters.AddWithValue("@UserTypeCode", objects[4]);
                command.Parameters.AddWithValue("@ActualCodeOrRoll", objects[5]);

                command.Connection.Open();
                count = command.ExecuteNonQuery();
                command.Connection.Close();
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }

    public static SqlDataReader GetUserByUsername(string username)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }

    public static SqlDataReader GetUserByUserTypeCode(string userTypeCode)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE UserTypeCode = @UserTypeCode";
                command.Parameters.AddWithValue("@UserTypeCode", userTypeCode);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }
}

If anyone closely examine the these classes he can understand that, O-R Mapper layer needs the reference of BusinessObject-layer. BusinessObject- layer also needs a reference of O-R Mapper-layer.

This should create a circular dependency.

How can I avoid this problem?

Someone suggested using plain Data Transfer Objects (DTO). But, as far as I know, according to OOP, attributes and functionality of a real-world object should be grouped together as a class. If I use DTO then how can I encapsulate functionality into a class? Moreover I am creating another class without any attribute (BO). To me that is breach of OOP in both ways. If I do so, then what is OOP for in this world? The same answer can be applied for "UserManager" classes.

I found a blog.

It discusses about implementing interfaces. Define a separate interface, implement it in your data class in the BusinessObject and program against your interface in the BusinessObject and in the OR-Mapper layer.

But I could not do this.

Can anyone show me that with a practical example?

+2  A: 

If the OR Mapper is actually doing OR, then it probably doesn't need a reference to the BL - it just needs to know the Type(s) that is (are) involved. But that is a side issue...

The main answer to this type of issue is "Inversion of Control" / "Dependency Injection", presumably snipping everything under the BL - so the BL depends only on an interface (defined in a base assembly), but doesn't know about the concrete OR/DA/RDBMS (they are supplied by the IoC/DI).

This is a big topic, so I'm being intentionally vague. Personally I like StructureMap, but there are lots of IoC/DI tools available.

Note that technically it is possible to create circular assembly references; it is a really bad idea, though - and the tools will (intentionally) fight you at every step.

Marc Gravell
+3  A: 

I think there are a few things you can do that together could help with your design. I also think that you might want to read up on Dependency Injection as perhaps providing a better design pattern for what you want to do.

Assuming you just want to make what you have work though:

  • First, remove the static methods from your User class, since they 'create' users, and therefore are best just left on the UserMapper.

  • After that, there will still be a number of methods potentially that use UserMapper functionality from the User class. Create an interface IUserLookup (or something) that supports the UserNameExists and UserNamePasswordExists methods; put this interface in the same project as the User class.

  • Implement the IUserLookup on the UserMapper class, and then 'inject' it into the User class instances it creates with the static methods through a constructor, so basically, as the UserMapper creates User objects, it gives them a reference to the IUserLookup interface that it implements itself.

In this way, User only uses methods on IUserLookup, which is in the same solution, so no reference needed. And UserMapper references this solution, so it can create User objects and implement the IUserLookup interface.

jerryjvl
After implementing the idea of "Interface-Implementation" in c# and VS2005 I actually found that, implementing interfaces is solving the problem but not fulfilling my target. My target is to work only with BO-layer/assembly from the UI layer/Assembly. So that I can maintain a clean layer-to-layer reference.Coz I don't want a reference to be added both for BO-layer/assembly and ORMapper-layer/assembly in the UI-layer/assembly. I only want to work with BO-layer/assembly from within UI-layer/assembly.Meanwhile someone suggested me that, it can only be possible with using Reflection, not DI.
In essence you are then making your ORM layer de-facto part of the BO layer. The idea or ORM/BO is that you make the BO layer responsible for the functionality that the business objects embody, but the ORM layer is responsible for materialising and storing the objects. Hiding ORM behind BO is going to break the separation of concerns the ORM/BO split is supposed to be for.
jerryjvl
A: 

In the code you have submitted above, there's no evidence of circular dependency.

When your call travels from upper layers to bottom layers... your object transformed into specialization which is appropriate to each layer (However in your case you are dealing with primitives on each layer... atleast in the submitted code)... And when your call returns it should be from specialization towards generalization....

This could be vice versa and there is no circular dependency issues if a single path is observed in such a way. However if with in any layered you try to implement Specilization scenario for both side information traveling path then we've a problem since each layer will depend and required the reference of its enclosing layer.

But in your code there's no such evidence of circular dependency. However if there's such a case this can be avoided by implementing interface layer or Adapter Pattern (interface layer is an adapter pattern).

For instance we have a Layer InformationTravel (IT)...(OK I understand this not sounds too good)

| UI | | | InformationTravel | ** | | Business Object | | | OR-Mapper | | | Data Access | | | RDBMS |

What you do is for your User Business Object, declare an interface IUser and implement it in User business object....

Then BO have a reference of IT. Object creation should only be in BO layer which implements an interface from IT.. This is perfectly OK. When you need to pass this object to your ORM, you pass it by slicing it to an interface implemented in IT and when you receive it back you again return the same object after doing necessary modification.

But this again impose an implication that you can't create your BO at multiple layers since only BO have the implementation of the interface. However if you really can't avoid this then you have to provide the implementation at multiple location or use Adapter pattern (Encapsulate your object into another object which is expected by the client).

S M Kamran
Implementing interfaces is solving my problem but not fulfilling my target. My target is to work only with BO-layer/assembly from the UI layer/Assembly. So that I can maintain a clean layer-to-layer reference. Coz I don't want a reference to be added both for BO-layer/assembly and ORMapper-layer/assembly in the UI-layer/assembly. I only want to work with BO-layer/assembly from within UI-layer/assembly. Meanwhile someone suggested me that, it can only be possible with using Reflection, not DI. Is that true?
A: 

To prevent circular reference between assemblies you should use interfaces .For example if your OR-Mapper needs to invoke some members of BL you should recognize these members and put them in one or several interfaces and put them either in an assembly (Interfaces for example) or in your OR-Mapper assembly and let your BL objects implement them then there will be no need to reference your BL in your OR-Mapper assembly.

Beatles1692
A: 

Please find this link http://msdn.microsoft.com/en-us/library/aa479312.aspx#issue3

renjucool