domain driven design - implementing - acceso a datos en DDD?
implementing domain-driven design (6)
Después de leer los libros de Evan y Nilsson, todavía no estoy seguro de cómo administrar el acceso a los datos en un proyecto impulsado por un dominio. ¿Deberían los métodos CRUD ser parte de los repositorios, es decir, OrderRepository.GetOrdersByCustomer (cliente) o deberían ser parte de las entidades: Customer.GetOrders (). El último enfoque parece más OO, pero distribuirá el acceso a datos para un solo tipo de entidad entre múltiples objetos, es decir, Customer.GetOrders (), Invoice.GetOrders (), ShipmentBatch.GetOrders (), etc. ¿Qué hay de Insertar y actualizar?
DDD por lo general prefiere el patrón de repositorio sobre el patrón de registro activo al que insinúa con Customer.Save.
Una desventaja del modelo de Active Record es que presupone un modelo de persistencia único, salvo algún código particularmente intrusivo (en la mayoría de los idiomas).
La interfaz del repositorio se define en la capa de dominio, pero no sabe si sus datos están almacenados en una base de datos o no. Con el patrón de repositorio, puedo crear un InMemoryRepository para poder probar la lógica de dominio de forma aislada y usar la inyección de dependencia en la aplicación para que la capa de servicio cree una instancia de un SqlRepository, por ejemplo.
Para muchas personas, tener un repositorio especial para probar suena tonto, pero si usa el modelo de repositorio, puede descubrir que realmente no necesita una base de datos para su aplicación en particular; a veces un simple FileRepository hará el truco. Casarse contigo mismo en una base de datos antes de saber que lo necesitas es potencialmente limitante. Incluso si se necesita una base de datos, es mucho más rápido ejecutar pruebas en un InMemoryRepository.
Si no tiene mucho en el camino de la lógica de dominio, probablemente no necesite DDD. ActiveRecord es bastante adecuado para muchos problemas, especialmente si tiene datos en su mayoría y solo un poco de lógica.
Incluso en un DDD, mantendría las clases y rutinas de acceso a datos separadas de las entidades.
Las razones son,
- La testabilidad mejora
- Separación de preocupaciones y diseño modular
- Más sostenible a largo plazo, a medida que agrega entidades, rutinas
No soy un experto, solo mi opinión.
Lo he hecho en ambos sentidos, mi enfoque preferido ahora es el método persistente ignorante (o PONO - Plain Ole ''.Net Object) donde las clases de dominio solo están preocupadas por ser clases de dominio. No saben nada sobre cómo persisten o incluso si persisten. Por supuesto, tienes que ser pragmático sobre esto a veces y tener en cuenta cosas como un Id (pero incluso entonces solo uso un super-tipo de capa que tiene Id para que pueda tener un único punto donde vivan cosas como el valor predeterminado)
La razón principal de esto es que me esfuerzo por seguir el principio de responsabilidad única. Siguiendo este principio, he encontrado que mi código es mucho más comprobable y fácil de mantener. También es mucho más fácil hacer cambios cuando son necesarios ya que solo tengo una cosa en que pensar.
Una cosa de la que hay que estar pendiente es la infinidad de métodos que pueden sufrir los repositorios. GetOrderbyCustomer .. GetAllOrders .. GetOrders30DaysOld .. etc etc. Una buena solución a este problema es observar el patrón Query Object. Y luego sus repositorios pueden simplemente tomar un objeto de consulta para ejecutar.
También recomendaría investigar algo como NHibernate. Incluye muchos de los conceptos que hacen que los repositorios sean tan útiles (mapa de identidad, caché, objetos de consulta ...)
Lo molesto con la aplicación de DDD & P de Nilsson es que siempre comienza con "No haría eso en una aplicación del mundo real, pero ..." y luego sigue su ejemplo. Volver al tema: Creo que OrderRepository.GetOrdersByCustomer (cliente) es el camino a seguir, pero también hay una discusión en la lista de correo de ALT.Net ( http://tech.groups.yahoo.com/group/altdotnet/ ) acerca de DDD.
Retrocedamos por un segundo. Evans recomienda que los repositorios devuelvan raíces agregadas y no solo entidades. Entonces, suponiendo que su Cliente sea una raíz agregada que incluya Órdenes, entonces cuando recogió al cliente de su repositorio, las órdenes llegaron junto con él. Tendrás acceso a las órdenes navegando por la relación entre el Cliente y las Órdenes.
customer.Orders;
Entonces, para responder a su pregunta, las operaciones CRUD están presentes en repositorios raíz agregados.
CustomerRepository.Add(customer);
CustomerRepository.Get(customerID);
CustomerRepository.Save(customer);
CustomerRepository.Delete(customer);
Los métodos CRUD-ish deben ser parte del Repository ... ish. Pero creo que deberías preguntarte por qué tienes un montón de métodos CRUD. ¿Qué es lo que realmente hacen? ¿Para qué son realmente ? Si realmente menciona los patrones de acceso a los datos que usa su aplicación, creo que hace que el repositorio sea mucho más útil y evita que tenga que hacer una cirugía de escopeta cuando ciertos tipos de cambios suceden a su dominio.
CustomerRepo.GetThoseWhoHaventPaidTheirBill()
// or
GetCustomer(new HaventPaidBillSpecification())
// is better than
foreach (var customer in GetCustomer()) {
/* logic leaking all over the floor */
}
Los métodos de tipo "Guardar" también deben ser parte del repositorio.
Si tiene raíces agregadas, esto evita que tenga una explosión de repositorio, o que la lógica se extienda por todas partes: no tiene 4 x # de patrones de acceso a datos de entidades, solo los que realmente usa en las raíces agregadas.
Esa es mi $ .02.