Actualmente tengo un repositorio para casi todas las tablas en la base de datos y me gustaría alinearme más con DDD reduciéndolos a raíces agregadas solamente.
Supongamos que tengo las siguientes tablas User
y Phone
. Cada usuario puede tener uno o más teléfonos. Sin la noción de raíz agregada, podría hacer algo como esto:
//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);
El concepto de raíces agregadas es más fácil de entender en papel que en la práctica. Nunca tendré números de teléfono que no pertenezcan a un usuario, así que ¿tendría sentido eliminar el PhoneRepository e incorporar métodos relacionados con el teléfono en el UserRepository? Suponiendo que la respuesta sea sí, voy a reescribir el ejemplo de código anterior.
¿Puedo tener un método en UserRepository que devuelva números de teléfono? O debería devolver siempre una referencia a un Usuario y luego atravesar la relación a través del Usuario para llegar a los números de teléfono:
List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone
Independientemente de la forma en que adquiera los teléfonos, suponiendo que modifique uno de ellos, ¿cómo hago para actualizarlos? Mi conocimiento limitado es que los objetos debajo de la raíz deben actualizarse a través de la raíz, lo que me llevaría hacia la opción # 1 a continuación. Aunque esto funcionará perfectamente bien con Entity Framework, parece extremadamente poco descriptivo, porque al leer el código no tengo idea de lo que estoy actualizando, aunque Entity Framework mantiene la pestaña de los objetos modificados dentro del gráfico.
UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);
Por último, en el supuesto tengo varias tablas de búsqueda que no son realmente vinculados a cualquier cosa, como por ejemplo CountryCodes
, ColorsCodes
, SomethingElseCodes
. Podría usarlos para completar menús desplegables o por cualquier otra razón. ¿Son estos repositorios independientes? ¿Se pueden combinar en algún tipo de agrupación / repositorio lógico como CodesRepository
? ¿O va en contra de las mejores prácticas?
Respuestas:
Se le permite tener cualquier método que desee en su repositorio :) En los dos casos que menciona, tiene sentido devolver al usuario con la lista de teléfonos completa. Normalmente, el objeto de usuario no se completará completamente con toda la información secundaria (por ejemplo, todas las direcciones, números de teléfono) y es posible que tengamos diferentes métodos para obtener el objeto de usuario con diferentes tipos de información. Esto se conoce como carga diferida.
User GetUserDetailsWithPhones() { // Populate User along with Phones }
Para la actualización, en este caso, se actualiza al usuario, no al número de teléfono en sí. El modelo de almacenamiento puede almacenar los teléfonos en una tabla diferente y de esa manera puede pensar que solo los teléfonos se están actualizando, pero ese no es el caso si lo piensa desde la perspectiva de DDD. En cuanto a la legibilidad, mientras que la línea
por sí solo no transmite lo que se está actualizando, el código anterior dejaría en claro lo que se está actualizando. Además, lo más probable es que sea parte de una llamada al método de interfaz que puede significar lo que se está actualizando.
Para las tablas de búsqueda, e incluso de otra manera, es útil tener GenericRepository y usarlo. El repositorio personalizado puede heredar del GenericRepository.
public class UserRepository : GenericRepository<User> { IEnumerable<User> GetUserByCustomCriteria() { } User GetUserDetailsWithPhones() { // Populate User along with Phones } User GetUserDetailsWithAllSubInfo() { // Populate User along with all sub information e.g. phones, addresses etc. } }
Busque Generic Repository Entity Framework y encontrará muchas buenas implementaciones. Utilice uno de esos o escriba el suyo propio.
fuente
Su ejemplo en el repositorio Aggregate Root está perfectamente bien, es decir, cualquier entidad que no pueda existir razonablemente sin depender de otra no debería tener su propio repositorio (en su caso, Teléfono). Sin esta consideración, puede encontrarse rápidamente con una explosión de repositorios en una asignación 1-1 a tablas de base de datos.
Debería considerar el uso del patrón de Unidad de trabajo para los cambios de datos en lugar de los repositorios en sí, ya que creo que le están causando cierta confusión sobre la intención cuando se trata de persistir los cambios en la base de datos. En una solución EF, la Unidad de trabajo es esencialmente un contenedor de interfaz alrededor de su contexto EF.
Con respecto a su repositorio de datos de búsqueda, simplemente creamos un ReferenceDataRepository que se hace responsable de los datos que no pertenecen específicamente a una entidad de dominio (países, colores, etc.).
fuente
Si el teléfono no tiene sentido sin el usuario, es una entidad (si le importa su identidad) u objeto de valor y siempre debe modificarse a través del usuario y recuperarse / actualizarse juntos.
Piense en las raíces agregadas como definidores de contexto: dibujan contextos locales pero están en el contexto global (su aplicación) ellos mismos.
Si sigue un diseño basado en dominios, se supone que los repositorios son 1: 1 por raíces agregadas.
No hay excusas.
Apuesto a que estos son problemas que estás enfrentando:
No estoy seguro de cómo solucionar el primer problema, pero he notado que solucionar el segundo soluciona el primero lo suficientemente bien. Para entender lo que quiero decir con centrado en el comportamiento, pruebe este artículo .
Ps Reducir el repositorio a raíz agregada no tiene sentido.
Pps Evitar
"CodeRepositories"
. Eso conduce a un código de procedimiento centrado en datos.Ppps Evite el patrón de unidad de trabajo. Las raíces agregadas deben definir los límites de las transacciones.
fuente
Esta es una pregunta antigua, pero se cree que vale la pena publicar una solución simple.
static void updateNumber (int userId, string oldNumber, string newNumber)
static void updateNumber(int userId, string oldNumber, string newNumber) { using (MyContext uow = new MyContext()) // Unit of Work { DbSet<User> repo = uow.Users; // Repository User user = repo.Find(userId); Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault(); oldPhone.Number = newNumber; uow.SaveChanges(); } }
fuente
Si una entidad de teléfono solo tiene sentido junto con un usuario raíz agregado, entonces también creo que tiene sentido que la operación para agregar un nuevo registro de teléfono sea responsabilidad del objeto de dominio de usuario a través de un método específico (comportamiento DDD) y eso podría tiene perfectamente sentido por varias razones, la razón inmediata es que debemos verificar que el objeto Usuario existe, ya que la entidad Teléfono depende de su existencia y tal vez mantener un bloqueo de transacción mientras se realizan más verificaciones de validación para garantizar que ningún otro proceso haya eliminado el agregado raíz antes hemos terminado de validar la operación. En otros casos, con otros tipos de agregados raíz, es posible que desee agregar o calcular algún valor y mantenerlo en las propiedades de columna del agregado raíz para un procesamiento más eficiente por otras operaciones más adelante.
Además, si desea utilizar métodos que recuperen todos los teléfonos independientemente de los usuarios que los posean, aún puede hacerlo a través del repositorio de usuarios, solo necesita un método que devuelva todos los usuarios como IQueryable, luego puede asignarlos para obtener todos los teléfonos de los usuarios y hacer consulta con eso. Así que ni siquiera necesitas un PhoneRepository en este caso. Además, prefiero usar una clase con método de extensiones para IQueryable que pueda usar en cualquier lugar, no solo desde una clase de Repositorio, si quisiera abstraer consultas detrás de métodos.
Solo una advertencia para poder eliminar entidades de teléfono usando solo el objeto de dominio y no un repositorio de teléfono, debe asegurarse de que el UserId sea parte de la clave principal del teléfono o, en otras palabras, la clave principal de un registro de teléfono es una clave compuesta compuesto por UserId y alguna otra propiedad (sugiero una identidad generada automáticamente) en la entidad Phone. Esto tiene sentido intuitivamente ya que el registro del teléfono es "propiedad" del registro del usuario y su eliminación de la colección de navegación del usuario equivaldría a su eliminación completa de la base de datos.
fuente