Pregunta

Actualmente tengo clases de servicio que se parecen en algo a esto

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("....");
       .....
    }
}

Y clases de DAL que se ven así

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);
    }
}

Me preguntaba si se consideraba un mal diseño el hecho de que la capa de servicio hablara con otros objetos de la capa de servicio y Dal hablara con otros objetos Dal.

Gracias de antemano

¿Fue útil?

Solución

Dado el diseño de sus ejemplos, se encontrará con lo que me gusta llamar infierno de dependencia. Sin duda, es una opción seguir el camino que está tomando, pero conducirá a una arquitectura altamente acoplada que probablemente será muy difícil de mantener y refactorizar. Sin embargo, si abstrae un poco más, puede simplificar su arquitectura, organizar responsabilidades un poco más y separar las preocupaciones de manera que la administración de sus dependencias sea mucho más fácil.

El UserService, AssignmentService y LocationService parecen servicios de estilo CRUD. Un término más apropiado para ellos sería Entity Services. Un servicio de entidad debe ser el único responsable de las operaciones de CRUD de la entidad inmediata, y nada más. Las operaciones que involucran múltiples entidades, relaciones de entidades, etc. pueden ser enviadas a un servicio de nivel superior que puede orquestar operaciones de mayor escala. Estos a menudo se denominan orquestación o servicios de tareas.

Recomendaría un enfoque como el siguiente. Los objetivos aquí son simplificar cada servicio para darle el alcance de responsabilidad más pequeño y las dependencias de control. Simplifique sus contratos de servicio para reducir las responsabilidades de los servicios de su entidad existente y agregue dos nuevos servicios:

// 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 ...
}

El proceso de crear un usuario y asociarlo con una ubicación y asignación pertenecería al UserManagementService, que compondría los servicios de la entidad de nivel inferior:

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

Las capas conceptuales y el flujo de datos / objetos que resultan de esta arquitectura serían los siguientes:

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

Un par de cosas clave a tener en cuenta aquí con respecto a la composición y las dependencias. Al agregar el UserManagementService y componer los servicios de la entidad dentro de él, logrará lo siguiente:

  1. Eliminación del acoplamiento entre servicios de entidad.
  2. Reducción del volumen de dependencias para cada servicio de entidad.
    • Solo dependen de su DAL y posiblemente de su infraestructura común.
  3. Las dependencias ahora son unidireccionales: todas las dependencias son 'hacia abajo', nunca 'horizontal' o 'hacia arriba'.
    • Esta sencilla regla proporciona un mecanismo muy limpio mediante el cual las dependencias indisciplinadas pueden eliminarse por completo.
  4. Las reglas de asociar a un usuario con una asignación y una ubicación se eliminan de las entidades y se presionan más.
    • Esto proporciona composiciones más flexibles y fomenta la reutilización del código.
    • Se pueden escribir otros servicios como UserManagementService que componen User, Assignment, y Location y / u otras entidades de manera diferente para cumplir con diferentes reglas comerciales y resolver diferentes problemas.
  5. Incluso los servicios de nivel superior pueden escribirse sobre UserManagementService y servicios similares, componiéndolos de una manera similar, creando flujos de trabajo de nivel más alto con un esfuerzo mínimo.

Si es cuidadoso en cómo diseña y escribe cada nivel de servicios, puede proporcionar muchas capas de responsabilidad, complejidad y composición diferentes. Una aplicación se vuelve menos sobre escribir reglas de negocios, y más sobre componer partes para crear un comportamiento de negocios. Si es necesario escribir una parte, generalmente se puede escribir componiendo otras partes y posiblemente agregando una pequeña cantidad de comportamiento adicional. La construcción de una aplicación se vuelve mucho más sencilla, y es mucho más fácil construir partes totalmente funcionales, autónomas, reutilizables, más fáciles de probar de forma aislada y más fáciles de implementar.

También habrás alcanzado la Orientación de servicio en el sentido más verdadero (de acuerdo con Thomas Erl de todos modos), junto con todos sus beneficios. ;)

Otros consejos

No sé a qué te refieres con Dal, pero en general, debes equilibrar el nivel de cohesion y acoplamiento en su aplicación.

En este caso, me preguntaría por qué una instancia de un UserService puede validar otros IUsers. Eso parece ser un acoplamiento que podría ser problemático. Sin embargo, no conozco tu aplicación, por lo que podría estar equivocado.

Dibuje un diagrama de clase para que sea más fácil ver el acoplamiento entre las clases.

Podría seguir un modelo similar al " DataContext " el modelo seguido de LINQ2SQL o Entityframework, es decir, tiene un Contexto que realiza un seguimiento de las entidades, como " Usuario " Su capa de servicio, a su vez, solo habla con el Contexto para realizar consultas entre entidades o realizar operaciones entre entidades si es necesario. De esta forma, sus entidades individuales, como el " Usuario " la entidad no necesitará tener conocimiento directo (acoplamiento) a otra " no relacionada " Las entidades y el contexto exponen las entidades al cliente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top