Al repositorio o no al repositorio

10

Cuando aprendí por primera vez sobre el diseño impulsado por dominio, también me presentaron los patrones de repositorio y unidad de trabajo que una vez parecían ser de primera categoría para los niños geniales que lanzaban consultas SQL como cavernícolas contra bases de datos. Cuanto más profundicé en ese tema, más aprendí que ya no parecen ser necesarios debido a ORM como EF y NHibernate que implementan tanto la unidad de trabajo como los repositorios en una API, llamada sesión o contexto.

Ahora no estoy seguro de qué hacer. Al repositorio o no al repositorio. Realmente entiendo el argumento de que tales abstracciones con fugas solo complican las cosas al tiempo que no agregan absolutamente nada que pueda simplificar el acceso a los datos, sin embargo, no me parece correcto combinar todos los aspectos posibles de mi aplicación, por ejemplo, con Entity Framework . Por lo general, sigo algunas pautas simples:

  1. La capa de dominio es el corazón del sistema, que contiene entidades, servicios, repositorios ...
  2. La capa de infraestructura proporciona implementaciones de interfaces de dominio de un problema de infraestructura, por ejemplo, archivos, bases de datos, protocolos.
  3. La capa de aplicación aloja una raíz de composición que conecta las cosas y organiza todo.

Mis soluciones generalmente se ven así:

Domain.Module1
Domain.Module2
    IModule2Repo
    IModule2Service
    Module2
Infrastructure.Persistence
    Repositories
        EntityFrameworkRepositoryBase
MyApp
    Boostrapper
        -> inject EntityFrameworkRepositoryBase into IRepository etc.

Mantengo mi capa de dominio limpia mediante el uso de un IRepository<'T>dominio que no depende de nada más que me diga cómo acceder a los datos. Cuando ahora haría una implementación concreta de IModule2Serviceeso que requiere acceso a datos, tendría que inyectar DbContexty, mediante esto, acoplarlo directamente a la capa de infraestructura. ( Al llegar al proyecto de Visual Studio, ¡esto puede terminar realmente complicado debido a las dependencias circulares! )

Además, ¿qué puede ser una alternativa a los depósitos y obras de obras ? CQRS? ¿Cómo se abstrae un marco infraestructural puro?

Acrotigma
fuente
1
Por otro lado, si estás hablando de 'Repositorio' como se describe en DDD, entonces EF y nHibernate no son repositorios. Claro, abstraen parcialmente el mecanismo de acceso a datos, pero hay mucho más en un repositorio que eso .
Eric King

Respuestas:

4

La ingeniería se trata de compromisos. Y también lo es el desarrollo de software. En este momento, creo que solo otra opción, que sería más simple, es trabajar directamente con ORM. Pero como dijiste, eso podría encerrarte en un marco de persistencia específico.

Por lo tanto, debe preguntarse "¿Vale la pena la complejidad adicional del desacoplamiento de su código de la persistencia"? Cada vez que escucho a las personas decir que quieren desacoplar su código de la persistencia, pregunto "¿Cuántas veces en su carrera cambió su marco de persistencia?"

El problema que veo con los repositorios es que dificultan los cambios comunes. P.ej. agregando una nueva forma de consultar un agregado. Y hacen cambios poco comunes (supuestamente) más fáciles. P.ej. implementación cambiante de repositorios.

Luego también está el argumento de la prueba de unidad, a lo que digo "Si el marco de persistencia no le permite burlarse de una base de datos, ya sea en memoria o localmente, entonces no vale la pena usar el marco en absoluto".

Eufórico
fuente
1
El objetivo del repositorio es desacoplar la aplicación de la persistencia y no hay complejidad involucrada. Y cambio los detalles de persistencia al menos una vez, porque el acceso db es lo último que codifico, hasta ese punto que estoy usando en repositorios de memoria. Además, hay una trampa muy sutil cuando estás usando un detalle de persistencia directamente. Sus objetos comerciales tenderán a estar diseñados para ser compatibles con ellos, en lugar de ser independientes. Y eso se debe a que una entidad rica y adecuadamente encapsulada no puede restaurarse directamente desde ningún almacenamiento (excepto la memoria) sin (algunas feas) soluciones alternativas
MikeSW
Acerca de agregar una nueva forma de consultar una raíz agregada o cualquier entidad, es por eso que apareció CQRS. Personalmente, mantengo el Repositorio para fines de dominio y uso manejadores de consultas para consultas reales. Y esos manejadores están muy estrechamente acoplados al db y ofc son muy eficientes en lo que hacen.
MikeSW
3

Soy pro-repositorio, aunque me he alejado de los patrones genéricos de repositorio. En cambio, alineo mis repositorios con la función comercial a la que sirven. Los repositorios no están destinados a abstraer el ORM, ya que esto no es algo que espere cambiar, y al mismo tiempo evito hacer un repositorio demasiado granular. (Es decir, CRUDO) En cambio, mis repositorios tienen dos o tres propósitos clave:

  • Recuperación de datos
  • Creación de datos
  • Borrado duro

Para la recuperación de datos, el repositorio siempre regresa IQueryable<TEntity>. Para la creación de datos, devuelve TEntity. El repositorio maneja mi filtrado de nivel base, como el estado "activo" de autorización para sistemas que usan patrones de borrado suave y el estado "actual" para sistemas que usan datos históricos. La creación de datos es responsable de garantizar que las referencias requeridas se resuelvan y asocien y que la entidad esté configurada y lista para funcionar.

La actualización de datos es responsabilidad de la lógica de negocios que trabaja con las entidades en cuestión. Esto puede incluir cosas como reglas de validación. No intento encapsular eso en un método de repositorio.

La eliminación en la mayoría de mis sistemas es una eliminación suave, por lo que se incluiría en la actualización de datos. (IsActive = false) En el caso de borrados duros, esto sería una línea en el Repositorio.

¿Por qué repositorios? Prueba de habilidad. Claro, DbContext se puede burlar, pero es más simple burlarse de una clase que devuelveIQueryable<TEntity>. También juegan bien con el patrón UoW, personalmente uso el patrón DbContextScope de Mehdime para determinar la unidad de trabajo en el nivel que quiero (Controladores Ie en MVC) y dejar que mis controladores y clases de servicio auxiliar utilicen los repositorios sin necesidad de pasar referencias a el UoW / dbContext alrededor. El uso de IQueryable significa que no necesita muchos métodos de envoltura en el repositorio, y su código puede optimizar cómo se van a consumir los datos. Por ejemplo, el repositorio no necesita exponer métodos como "Existe" o "Contar", o tratar de envolver entidades con otras POCO en los casos en que desee subconjuntos de datos. Ni siquiera necesitan manejar opciones de carga ansiosa para datos relacionados que puede o no necesitar. Al pasar IQueryable, el código de llamada puede:

.Any()
.Count()
.Include() // Generally avoided, instead I use .Select()
.Where()
.Select(x => new ViewModel or Anon. Type)
.Skip().Take()
.FirstOrDefault() / .SingleOrDefault() / .ToList()

Muy flexible, y desde un punto de vista de prueba, mi repositorio simulado simplemente necesita devolver Listas de objetos de entidad poblados.

En cuanto a los repositorios genéricos, me he alejado de estos en su mayor parte porque cuando terminas con un repositorio por tabla, tus controladores / servicios terminan con referencias a varios repositorios para hacer una acción comercial. En la mayoría de los casos, solo uno o dos de estos repositorios realizan operaciones de escritura (siempre que esté utilizando las propiedades de navegación correctamente), mientras que el resto admite aquellos con Lecturas. Prefiero tener algo así como un repositorio de pedidos que sea capaz de leer y crear pedidos, y leer cualquier búsqueda relevante, etc. (objetos de clientes livianos, productos, etc.) como referencia al crear un pedido, que alcanzar 5 o 6 repositorios diferentes. Puede violar los puristas de DNRY, pero mi argumento es que el propósito del repositorio es servir a la creación de Órdenes que incluye las referencias relacionadas.Repository<Product>para obtener productos donde para la base de un pedido solo necesito una entidad con un puñado de campos. Mi OrderRepository puede tener un .GetProducts()método IQueryable<ProductSummary>que devuelve, lo que me parece más agradable que uno Repository<Product>que termina teniendo varios métodos "Get" para probar y servir diferentes áreas de las necesidades de la aplicación, y / o alguna expresión compleja de filtrado de paso.

Opto por un código más simple que sea fácil de seguir, probar y ajustar. Se puede abusar de malas maneras, pero prefiero tener algo donde los abusos sean fáciles de detectar y corregir que tratar de "bloquear" el código de una manera que no se pueda abusar, fallar y luego tener algo que sea una pesadilla para poder hacer lo que el cliente paga para que lo haga al final. :)

Steve Py
fuente