Diseño controlado por dominio: dependencias externas en el problema de la entidad

23

Me gustaría iniciar Domain-Driven-Design, pero hay varios problemas que me gustaría resolver antes de comenzar :)

Imaginemos que tengo Grupos y Usuarios y cuando el usuario quiere unirse a un grupo, estoy llamando al groupsService.AddUserToGroup(group, user)método. En DDD debería hacerlo group.JoinUser(user), lo que se ve bastante bien.

El problema aparece si hay algunas reglas de validación para agregar un usuario, o algunas tareas externas deben iniciarse cuando el usuario se agrega al grupo. Tener estas tareas llevará a que la entidad tenga dependencias externas.

Un ejemplo podría ser: una restricción de que el usuario solo puede participar en 3 grupos como máximo. Esto requerirá llamadas DB desde dentro del método group.JoinUser para validar esto.

Pero el hecho de que una entidad dependa de algunos servicios / clases externos no me parece tan bueno y "natural".

¿Cuál es la forma correcta de lidiar con esto en DDD?

Shaddix
fuente

Respuestas:

15

Imaginemos que tengo Grupos y Usuarios y cuando el usuario quiere unirse a un grupo, llamo al método groupsService.AddUserToGroup (grupo, usuario). En DDD debería hacer group.JoinUser (usuario), que se ve bastante bien.

Pero DDD también lo alienta a usar servicios (sin estado) para realizar tareas, si la tarea en cuestión es demasiado compleja o no encaja en un modelo de entidad. Está bien tener servicios en la capa de dominio. Pero los servicios en la capa de dominio solo deben incluir la lógica empresarial. Las tareas externas y la lógica de la aplicación (como enviar un correo electrónico), por otro lado, deben usar el servicio de dominio en la capa de aplicación, en el que podría tener un servicio separado (aplicación) envolviéndolo, por ejemplo.

El problema aparece si hay algunas reglas de validación para agregar un usuario ...

¡Las reglas de validación pertenecen al modelo de dominio! Deben encapsularse dentro de los objetos de dominio (entidades, etc.).

... o algunas tareas externas deben iniciarse cuando el usuario se agrega al grupo. Tener estas tareas llevará a que la entidad tenga dependencias externas.

Si bien no sé de qué tipo de tarea externa está hablando, supongo que es algo así como enviar un correo electrónico, etc. Pero esto no es realmente parte de su modelo de dominio. Debería vivir en la capa de aplicación y ser entregado allí en mi humilde opinión. Puede tener un servicio en su capa de aplicación que opera en servicios de dominio y entidades para realizar esas tareas.

Pero el hecho de que una entidad dependa de algunos servicios / clases externos no me parece tan bueno y "natural".

No es natural y no debería estar sucediendo. La entidad no debe saber sobre cosas que no son su responsabilidad. Los servicios deben usarse para orquestar las interacciones entre entidades.

¿Cuál es la forma correcta de lidiar con esto en DDD?

En su caso, la relación probablemente debería ser bidireccional. Si el usuario se une al grupo o si el grupo toma al usuario depende de su dominio. ¿El usuario se une al grupo? ¿O el usuario se agrega a un grupo? ¿Cómo funciona en tu dominio?

De todos modos, tiene una relación bidireccional y, por lo tanto, puede determinar la cantidad de grupos a los que el usuario ya pertenece dentro del agregado de usuarios. Si pasa el usuario al grupo o el grupo al usuario es técnicamente trivial una vez que haya determinado la clase responsable.

La validación debe ser realizada por la entidad. Todo se llama desde un servicio de la capa de aplicación que también puede hacer cosas técnicas, como enviar correos electrónicos, etc.

Sin embargo, si la lógica de validación es realmente compleja, un servicio de dominio podría ser una mejor solución. En ese caso, encapsule las reglas de negocio allí y luego instálelo desde su capa de aplicación.

Halcón
fuente
Pero si movemos tanta lógica fuera de la entidad, ¿qué debería mantenerse dentro?
SiberianGuy
¡Las responsabilidades directas de la entidad! Si puede decir "el usuario puede unirse a un grupo", por ejemplo, es responsabilidad de la entidad usuaria. A veces hay que tomar decisiones de compensación por razones técnicas. Tampoco soy un gran admirador de las relaciones bidireccionales, pero a veces se adapta mejor al modelo. Así que escucha con atención cuando hables sobre el dominio. "Una entidad sí ..." "La entidad puede ..." Cuando escuche tales oraciones, entonces esas operaciones probablemente pertenezcan a la entidad.
Falcon
Además, sabe que necesita un servicio cuando dos o más objetos no relacionados de otra manera necesitan participar en una tarea para lograr algo.
Falcon
1
Gracias por tu respuesta, Falcon! Por cierto, siempre intenté usar servicios sin estado, así que estoy un paso más cerca de DDD :) Digamos que en un dominio esta operación UserJoinsToGroup pertenece al Grupo. El problema es que para validar esa operación necesito saber en cuántos grupos ese Usuario ya participa (para negar una operación si ya es> 3). Para saber que necesito consultar la base de datos. ¿Cómo puedo hacer eso desde la entidad del Grupo? Tengo algunos ejemplos más, cuando necesito tocar el DB en las operaciones que naturalmente deberían pertenecer a la entidad (los publicaré si es necesario :))
Shaddix
2
Bueno, si lo pienso: ¿qué pasa con una entidad GroupMembership? Puede ser construido por una fábrica y esta fábrica puede acceder a los depósitos. Eso sería bueno DDD y encapsula la creación de membresías. La fábrica puede acceder a los Repositorios, crea una Membresía y luego la agrega al usuario y al grupo respectivamente. Esta nueva entidad también podría encapsular privilegios. Quizás sea una buena idea.
Falcon
3

La forma en que abordaría el problema de la validación es de esta manera: cree un servicio de dominio llamado MembershipService:

class MembershipService : IMembershipService
{
   public MembershipService(IGroupRepository groupRepository)
   { 
     _groupRepository = groupRepository;
   }
   public int NumberOfGroupsAssignedTo(UserId userId)
   {
        return _groupsRepository.NumberOfGroupsAssignedTo(userId);
   }
}

La entidad del Grupo necesita ser inyectada IMemberShipService. Esto se puede hacer a nivel de clase o de método. Supongamos que lo hacemos a nivel de método.

class Group{

   public void JoinUser(User user, IMembershipService membershipService)
   {
       if(membershipService.NumberOfGroupsAssignedTo(user.UserId) >= 3)
         throw new BusinessException("User assigned to more than 3 groups. Cannot proceed");

       // do some more stuff
   }
}

El servicio de aplicación: GroupServicepuede inyectarse con la IMemberShipServiceinyección de Constructor, que luego puede pasar al JoinUsermétodo de la Groupclase.

Eklavya Gupta
fuente
1
Es posible que desee considerar formatear el código fuente en su publicación para facilitar la lectura
Benni