类设计输入

zSy*_*sis 2 c# class-design

我目前的服务类看起来像这样

public class UserService : IUserService 
{
    private IAssignmentService _assignmentService;
    private ILocationService _locationService;
    private IUserDal _userDal;
    private IValidationDictionary _validationDictionary;
    public UserService(IAssignmentService assignmentService, ILocationService locationService, IValidationDictionary validationDictionary)
    {
        this._assignmentService = assignmentService;
        this._locationService = locationService;
        this._userDAL = new UserDal();
        this._validationDictionary = validationDictionary;
    }

    .....

    private void ValidateUser(IUser user)
    {
       if (_locationService.GetBy(user.Location.Id) == null)
          _validationDictionary.AddError("....");
       if (_assignmentService.GetBy(user.Assignment.Id) == null)
          _validationDictionary.AddError("....");
       .....
    }
}
Run Code Online (Sandbox Code Playgroud)

和DAL类看起来像这样

public class UserDal: IUserDal
{
    private IAssignmentDal _assignmentDal;
    private ILocationDAL _locationDAL

    public UserDal()
    {
        this_assignmentDal = new AssignmentDal();
        this._locationDal = new LocationDal();
    }

    public int AddUser(IUser user)
    {
       // db call and insert user
       _locationDal.Add(user.Location);
       _assignmentDal.Add(user.Assignment);
    }

    public IUser GetUser(int id)
    {
       ..DB Call

       IUser user = new User() { userData, GetLocation(dr["Location_Id"]),GetAssignment([dr["Assignment_Id"]);
       return user
    }

    private ILocation GetLocation(int id)
    {
        return new LocationDal().GetById(id);
    }
    private IAssignment GetAssignment(int id)
    {
        return new AssignmentDal().GetById(id);
    }
}
Run Code Online (Sandbox Code Playgroud)

我想知道将服务层与其他服务层对象进行通信以及Dal与其他Dal对象进行通信是否被认为是错误的设计?

提前致谢

jri*_*sta 5

鉴于你的例子的设计,你将遇到我喜欢称为依赖地狱的东西.沿着您正在走的路线当然是一个选择,但它将导致高度耦合的架构,可能很难维护和重构.但是,如果您抽象一点,您可以简化您的体系结构,更多地组织职责,并以这样一种方式分离关注点,即管理依赖关系会更容易.

UserService,AssignmentService和LocationService看起来像CRUD样式的服务.对他们来说更合适的术语是实体服务.实体服务应全权负责直接实体的CRUD操作,而不是其他任何操作.涉及多个实体,实体关系等的操作可以推送到可以协调大规模操作的更高级别的服务.这些通常称为Orchestration或Task Services.

我建议采用如下方法.这里的目标是简化每个服务,使其具有最小的责任范围,并控制依赖关系.简化您的服务合同以减少现有实体服务的责任,并添加两项新服务:

// User Management Orchestration Service
interface IUserManagementService
{
    User CreateUser();
}

// User Entity Service
interface IUserService
{
    User GetByKey(int key);
    User Insert(User user);
    User Update(User user);
    void Delete(User user);
}

// User Association Service
interface IUserAssociationService
{
    Association FindByUser(User user);
    Location FindByUser(User user);
    void AssociateWithLocation(User user, Location location);
    void AssociateWithAssignment(User user, Assignment assignment);
}

// Assignment Entity Service
interface IAssignmentService
{
    Assignment GetByKey(int key);
    // ... other CRUD operations ...
}

// Location Entity Service
interface ILocationService
{
    Location GetByKey(int key);
    // ... other CRUD operations ...
}
Run Code Online (Sandbox Code Playgroud)

创建用户并将其与位置和分配相关联的过程属于UserManagementService,后者将组成较低级别的实体服务:

class UserManagementService: IUserManagementService
{
    public UserManagementService(IUserService userService, IUserAssociationService userAssociationService, IAssignmentService assignmentService, ILocationService locationService)
    {
        m_userService = userService;
        m_userAssociationService = userAssociationService;
        m_assignmentService = assignmentService;
        m_locationService = locationService;
    }

    IUserService m_userService;
    IUserAssociationService m_userAssociationService;
    IAssignmentService m_assignmentService;
    ILocationService m_locationService;

    User CreateUser(string name, {other user data}, assignmentID, {assignment data}, locationID, {location data})
    {
        User user = null;
        using (TransactionScope transaction = new TransactionScope())
        {
            var assignment = m_assignmentService.GetByKey(assignmentID);
            if (assignment == null)
            {
                assignment = new Assignment { // ... };
                assignment = m_assignmentService.Insert(assignment);
            }

            var location = m_locationService.GetByKey(locationID);
            if (location == null)
            {
                location = new Location { // ... };
                location = m_locationService.Insert(location);
            }

            user = new User
            {
                Name = name,
                // ...
            };
            user = m_userService.Insert(user);
            m_userAssociationService.AssociateWithAssignment(user, assignment);
            m_userAssociationService.AssociateWithLocation(user, location);
        }

        return user;
    }
}

class UserService: IUserService
{
    public UserService(IUserDal userDal)
    {
        m_userDal = userDal;
    }

    IUserDal m_userDal;

    public User GetByKey(int id)
    {
        if (id < 1) throw new ArgumentException("The User ID is invalid.");

        User user = null;
        using (var reader = m_userDal.GetByID(id))
        {
            if (reader.Read())
            {
                user = new User
                {
                    UserID = reader.GetInt32(reader.GerOrdinal("id")),
                    Name = reader.GetString(reader.GetOrdinal("name")),
                    // ...
                }
            }
        }

        return user;
    }

    public User Insert(User user)
    {
        if (user == null) throw new ArgumentNullException("user");
        user.ID = m_userDal.AddUser(user);
        return user;
    }

    public User Update(User user)
    {
        if (user == null) throw new ArgumentNullException("user");
        m_userDal.Update(user);
        return user;
    }

    public void Delete(User user)
    {
        if (user == null) throw new ArgumentNullException("user");
        m_userDal.Delete(user);
    }
}

class UserAssociationService: IUserAssociationService
{
    public UserAssociationService(IUserDal userDal, IAssignmentDal assignmentDal, ILocationDal locationDal)
    {
        m_userDal = userDal;
        m_assignmentDal = assignmentDal;
        m_locationDal = locationDal;
    }

    IUserDal m_userDal;
    IAssignmentDal m_assignmentDal;
    ILocationDal m_locationDal;

    public Association FindByUser(User user)
    {
        if (user == null) throw new ArgumentNullException("user");
        if (user.ID < 1) throw new ArgumentException("The user ID is invalid.");

        Assignment assignment = null;
        using (var reader = m_assignmentDal.GetByUserID(user.ID))
        {
            if (reader.Read())
            {
                assignment = new Assignment
                {
                    ID = reader.GetInt32(reader.GetOrdinal("AssignmentID")),
                    // ...
                };

                return assignment;
            }
        }
    }
}

class UserDal: IUserDal
{
    public UserDal(DbConnection connection)
    {
        m_connection = connection;
    }

    DbConnection m_connection;

    public User GetByKey(int id)
    {
        using (DbCommand command = connection.CreateCommand())
        {
            command.CommandText = "SELECT * FROM Users WHERE UserID = @UserID";
            var param = command.Parameters.Add("@UserID", DbType.Int32);
            param.Value = id;

            var reader = command.ExecuteReader(CommandBehavior.SingleResult|CommandBehavior.SingleRow|CommandBehavior.CloseConnection);
            return reader;                
        }
    }

    // ...
}

class AssignmentDal: IAssignmentDal
{

    public AssignmentDal(DbConnection connection)
    {
        m_connection = connection;
    }

    DbConnection m_connection;

    Assignment GetByUserID(int userID)
    {
        using (DbCommand command = connection.CreateCommand())
        {
            command.CommandText = "SELECT a.* FROM Assignments a JOIN Users u ON a.AssignmentID = u.AssignmentID WHERE u.UserID = @UserID";
            var param = command.Parameters.Add("@UserID", DbType.Int32);
            param.Value = id;

            var reader = command.ExecuteReader(CommandBehavior.SingleResult|CommandBehavior.SingleRow|CommandBehavior.CloseConnection);
            return reader;                
        }
    }

    // ...
}

// Implement other CRUD services similarly
Run Code Online (Sandbox Code Playgroud)

此架构产生的概念层和数据/对象流如下:

Task:                         UserManagementSvc
                                      ^
                                      |
             -------------------------------------------------
             |              |                 |              |
Entity:  UserSvc   UserAssociationsSvc   AssignmentSvc   LocationSvc
             ^       ^                        ^              ^
             |       |                        |              |
             ---------                        -              -
                 |                            |              |
Utility:      UserDal                   AssignmentDal   LocationDal
                 ^                            ^              ^
                 |                            |              |
                 ---------------------------------------------
                                       |
DB:                             (SQL Database)
Run Code Online (Sandbox Code Playgroud)

关于组合和依赖关系,需要注意几个关键事项.通过添加UserManagementService并在其中组合实体服务,您可以实现以下目标:

  1. 消除实体服务之间的耦合.
  2. 减少每个实体服务的依赖关系量.
    • 他们只依赖于他们的DAL和可能的共同基础设施.
  3. 依赖关系现在是单向的:所有依赖关系都是"向下",从不"水平"或"向上".
    • 这个简单的规则提供了一种非常干净的机制,通过该机制可以完全消除不循规依赖.
  4. 将用户与分配和位置相关联的规则从实体中移除并推高.
    • 这提供了更灵活的组合并鼓励代码重用.
    • 可以编写诸如UserManagementService之类的其他服务,其以不同方式组成用户,分配和位置和/或其他实体以满足不同的业务规则并解决不同的问题.
  5. 甚至更高级别的服务也可以写在UserManagementService和类似服务之上,以类似的方式组合它们,以最小的努力创建更高级别的工作流.

如果您在设计和编写每个级别的服务时都非常小心,那么您可以提供许多不同的职责,复杂性和可组合性.应用程序不再是编写业务规则,而是编写部件以创建业务行为.如果需要编写一个部分,通常可以通过编写其他部分来编写,并可能添加少量的其他行为.构建应用程序变得更加简单,并且构建功能齐全,自包含,可重用的部件更容易,这些部件更易于单独测试,并且更易于部署.

您也将在最真实的意义上实现服务导向(无论如何,根据Thomas Erl),以及它的所有好处.;)