vistas parciales net mvc component asp asp.net-mvc domain-driven-design onion-architecture

asp.net-mvc - parciales - render partial asp net core



Arquitectura de cebolla-Repositorio Vs Servicio? (5)

El patrón de repositorio media entre el dominio y las capas de mapeo de datos usando una interfaz similar a una colección para acceder a objetos de dominio.

Por lo tanto, los repositorios deben proporcionar una interfaz para la operación CRUD en las entidades de dominio. Recuerda que los Repositorios se ocupan de todo el Agregado.

Los agregados son grupos de cosas que pertenecen juntas. Una raíz agregada es lo que los mantiene a todos juntos.

Ejemplo de Order y OrderLines :

Las líneas de orden no tienen ninguna razón para existir sin su orden principal, ni pueden pertenecer a ninguna otra orden. En este caso, Orden y Líneas de orden probablemente serían un Agregado, y la Orden sería la Raíz Agregada

La lógica de negocios debe estar en las Entidades del Dominio, no en la capa de Repositorio, la lógica de la aplicación debe estar en la capa de servicio como lo mencionó, los servicios aquí desempeñan un papel de coordinador entre repositorios.

Estoy aprendiendo la conocida arquitectura de cebolla de Jeffrey Palermo. No específico para este patrón, pero no puedo ver claramente la separación entre repositorios y servicios de dominio. Entiendo (mal) que el repositorio se refiere al acceso a datos y que el servicio tiene más que ver con la capa empresarial (haga referencia a uno o más repositorios).

En muchos ejemplos, un repositorio parece tener algún tipo de lógica empresarial detrás, como GetAllProductsByCategoryId o GetAllXXXBySomeCriteriaYYY .

Para las listas, parece que el servicio es solo un contenedor en el repositorio sin ninguna lógica. Para las jerarquías (padre / hijos / hijos), es casi el mismo problema: ¿es la función del repositorio cargar la jerarquía completa?


Creo que el repositorio debe ser solo para las operaciones de CRUD.

public interface IRepository<T> { Add(T) Remove(T) Get(id) ... }

Por lo tanto, IRepository tendría: Agregar, Eliminar, Actualizar, Obtener, Obtener Todo y posiblemente una versión de cada uno de los que toma una lista, es decir, Agregar, Suprimir, Eliminar, etc.

Para realizar operaciones de recuperación de búsqueda, debe tener una segunda interfaz como un IFinder. Puede ir con una especificación, por lo que IFinder podría tener un método de Búsqueda (criterios) que tome criterios. O puede ir con cosas como IPersonFinder que definirían funciones personalizadas como: FindPersonByName, FindPersonByAge, etc.

public interface IMyObjectFinder { FindByName(name) FindByEmail(email) FindAllSmallerThen(amount) FindAllThatArePartOf(group) ... }

La alternativa sería:

public interface IFinder<T> { Find(criterias) }

Este segundo enfoque es más complejo. Es necesario definir una estrategia para los criterios. ¿Va a utilizar un lenguaje de consulta de algún tipo, o una asociación de clave-valor más simple, etc.? El poder total de la interfaz también es más difícil de entender simplemente mirándolo. También es más fácil filtrar implementaciones con este método, ya que los criterios podrían basarse en un tipo particular de sistema de persistencia, como por ejemplo, si toma una consulta SQL como criterio. Por otro lado, puede evitar que tenga que volver continuamente al IFinder porque ha tenido un caso de uso especial que requiere una consulta más específica. Digo que podría, porque su estrategia de criterios no necesariamente cubrirá el 100% de los casos de uso de consulta que pueda necesitar.

También puede decidir mezclar ambos, y tener un IFinder que defina un método de Búsqueda, e IMyObjectFinders que implementen IFinder, pero que también agreguen métodos personalizados como FindByName.

El servicio actúa como supervisor. Supongamos que necesita recuperar un elemento, pero también debe procesarlo antes de que se devuelva al cliente, y ese procesamiento puede requerir información que se encuentra en otros elementos. Así que el servicio recuperaría todos los elementos apropiados utilizando los Repositorios y los Buscadores, luego enviaría el elemento a procesar a los objetos que encapsula la lógica de procesamiento necesaria, y finalmente devolvería el elemento solicitado por el cliente. En algún momento, no se requerirá procesamiento ni recuperaciones adicionales, en tales casos, no es necesario tener un servicio. Puede hacer que los clientes llamen directamente a los Repositorios y a los Buscadores. Esta es una diferencia con la arquitectura Onion y Layered, en Onion, todo lo que está más afuera puede acceder a todo lo que está dentro, no solo la capa anterior.

Sería la función del repositorio cargar la jerarquía completa de lo que se necesita para construir correctamente el elemento que devuelve. Entonces, si su repositorio devuelve un elemento que tiene una Lista de otro tipo de elemento, ya debería resolverlo. Sin embargo, personalmente, me gusta diseñar mis objetos para que no contengan referencias a otros elementos, ya que hace que el repositorio sea más complejo. Prefiero que mis objetos guarden el Id. De otros elementos, de modo que si el cliente realmente necesita ese otro elemento, puede volver a consultarlo con el Repositorio adecuado dado el Id. Esto aplana todos los elementos devueltos por los repositorios, pero aún así le permite crear jerarquías si es necesario.

Podría, si realmente sintiera la necesidad, agregar un mecanismo de restricción a su Repositorio, de modo que pueda especificar exactamente qué campo del elemento necesita. Digamos que tienes una Persona y que solo te importa su nombre, podrías hacer Get (id, nombre) y el Repositorio no se molestaría en obtener todos los campos de la Persona, solo el campo del nombre. Hacer esto, sin embargo, agrega una complejidad considerable al repositorio. Y hacer esto con objetos jerárquicos es aún más complejo, especialmente si desea restringir los campos dentro de los campos de los campos. Así que realmente no lo recomiendo. La única buena razón para esto, para mí, serían los casos en los que el rendimiento es crítico y no se puede hacer nada más para mejorar el rendimiento.


El repositorio no es una puerta de acceso para acceder a la base de datos. Es una abstracción que le permite almacenar y cargar objetos de dominio de algún tipo de almacén de persistencia. (Base de datos, caché o incluso simple colección). Toma o devuelve los objetos del dominio en lugar de su campo interno, por lo que es una interfaz orientada a objetos.

No se recomienda agregar algunos métodos como GetAllProductsByCategoryId o GetProductByName al repositorio, porque agregará más y más métodos al repositorio a medida que aumente el número de casos / objetos. En su lugar, es mejor tener un método de consulta en el repositorio que tome una Especificación. Puede pasar diferentes implementaciones de la Especificación para recuperar los productos.

En general, el objetivo del patrón de repositorio es crear una abstracción de almacenamiento que no requiera cambios cuando cambien los casos de uso. Este artículo habla sobre el patrón del Repositorio en el modelado de dominios con gran detalle. Usted puede estar interesado.

Para la segunda pregunta: si veo un ProductRepository en el código, espero que me devuelva una lista de productos. También espero que cada una de las instancias del Producto esté completa. Por ejemplo, si el Producto tiene una referencia al objeto ProductDetail , esperaría que Product.getDetail() me devuelva una instancia de ProductDetail lugar de nula. Tal vez la implementación del repositorio cargue ProductDetail junto con Product, tal vez el método getDetail() invoque ProductDetailRepository sobre la marcha. Realmente no me importa como usuario del repositorio. También es posible que el Producto solo devuelva un ID de ProductDetail cuando llamo a getDetail() . Es perfecta multa desde el punto de vista contractual del repositorio. Sin embargo, complica el código de mi cliente y me obliga a llamar a ProductDetailRepository mi cuenta.

Por cierto, he visto muchas clases de servicio que solo envuelven las clases de repositorio en mi pasado. Creo que es un anti-patrón. Es mejor tener los llamadores de los servicios para utilizar los repositorios directamente.


En Domain Driven Design, el repositorio es responsable de recuperar todo el Agregado.


Mientras todavía estoy luchando con esto, quiero publicar como respuesta pero también acepto (y quiero) comentarios sobre esto.

En el ejemplo GetProductsByCategory(int id)

Primero, pensemos desde la necesidad inicial. Golpeamos un controlador, probablemente el CategoryController, así que tienes algo como:

public CategoryController(ICategoryService service) { // here we inject our service and keep a private variable. } public IHttpActionResult Category(int id) { CategoryViewModel model = something.GetCategoryViewModel(id); return View() }

Hasta aquí todo bien. Necesitamos declarar ''algo'' que crea el modelo de vista. Simplifiquemos y digamos:

public IHttpActionResult Category(int id) { var dependencies = service.GetDependenciesForCategory(id); CategoryViewModel model = new CategoryViewModel(dependencies); return View() }

ok que son las dependencias Tal vez necesitemos el árbol de categorías, los productos, la página, la cantidad total de productos, etc.

así que si implementamos esto en un repositorio, esto podría parecerse más o menos así:

public IHttpActionResult Category(int id) { var products = repository.GetCategoryProducts(id); var category = repository.GetCategory(id); // full details of the category var childs = repository.GetCategoriesSummary(category.childs); CategoryViewModel model = new CategoryViewModel(products, category, childs); // awouch! return View() }

en cambio, volver a servicios:

public IHttpActionResult Category(int id) { var category = service.GetCategory(id); if (category == null) return NotFound(); // var model = new CategoryViewModel(category); return View(model); }

mucho mejor, pero ¿qué es exactamente dentro de service.GetCategory(id) ?

public CategoryService(ICategoryRespository categoryRepository, IProductRepository productRepository) { // same dependency injection here public Category GetCategory(int id) { var category = categoryRepository.Get(id); var childs = categoryRepository.Get(category.childs) // int[] of ids var products = productRepository.GetByCategory(id) // this doesn''t look that good... return category; } }

Probemos otro enfoque, la unidad de trabajo, usaré Entity Framework como UoW y Repositories, por lo que no es necesario crearlos.

public CategoryService(DbContext db) { // same dependency injection here public Category GetCategory(int id) { var category = db.Category.Include(c=> c.Childs).Include(c=> c.Products).Find(id); return category; } }

Así que aquí estamos usando la sintaxis de ''consulta'' en lugar de la sintaxis del método, pero en lugar de implementar nuestro propio complejo, podemos usar nuestro ORM. Además, tenemos acceso a TODOS los repositorios, por lo que aún podemos hacer nuestra Unidad de trabajo dentro de nuestro servicio.

Ahora debemos seleccionar qué datos queremos, probablemente no quiero todos los campos de mis entidades.

El mejor lugar donde puedo ver que esto está sucediendo es en realidad en ViewModel, cada ViewModel puede necesitar mapear sus propios datos, así que cambiemos la implementación del servicio nuevamente.

public CategoryService(DbContext db) { // same dependency injection here public Category GetCategory(int id) { var category = db.Category.Find(id); return category; } }

Entonces, ¿dónde están todos los productos y categorías internas?

echemos un vistazo a ViewModel, recuerde que SOLO se asignarán datos a valores, si está haciendo algo más aquí, probablemente le está dando demasiada responsabilidad a su ViewModel.

public CategoryViewModel(Category category) { Name = category.Name; Id = category.Id; Products = category.Products.Select(p=> new CategoryProductViewModel(p)); Childs = category.Childs.Select(c => c.Name); // only childs names. }

puedes imaginarte el CategoryProductViewModel por ti mismo ahora mismo.

PERO (¿por qué siempre hay un pero?)

Estamos haciendo 3 hits de db, y estamos recuperando todos los campos de categoría debido a la búsqueda. También la carga perezosa debe estar habilitada. No es una solución real, ¿verdad?

Para mejorar esto, podemos cambiar buscar con dónde ... pero esto delegará el Single o Find a ViewModel, y también devolverá una IQueryable<Category> IQueryable, donde sabemos que debe ser exactamente uno.

Recuerda que dije "todavía estoy luchando?" esto es sobre todo por qué Para solucionar este problema, deberíamos devolver los datos exactos necesarios del servicio (también conocido como ... lo sabes ... ¡sí! El ViewModel).

Así que volvamos a nuestro controlador:

public IHttpActionResult Category(int id) { var model = service.GetProductCategoryViewModel(id); if (category == null) return NotFound(); // return View(model); }

Dentro del método GetProductCategoryViewModel , podemos llamar a métodos privados que devuelven las diferentes piezas y ensamblarlas como ViewModel.

esto es malo, ahora mis servicios saben sobre modelos de vista ... vamos a arreglar eso.

Creamos una interfaz, esta interfaz es el contrato real de lo que devolverá este método.

ICategoryWithProductsAndChildsIds // quite verbose, i know.

bueno, ahora solo tenemos que declarar nuestro ViewModel como

public class CategoryViewModel : ICategoryWithProductsAndChildsIds

Y ponerlo en práctica como queramos.

La interfaz parece tener demasiadas cosas, por supuesto, se puede ICategoryBasic con ICategoryBasic , IProducts , IChilds , o como quiera que quieras nombrarlas.

Entonces, cuando implementamos otro viewModel, podemos elegir hacer solo productos de IProducts . Podemos tener nuestros servicios con métodos (privados o no) para recuperar esos contratos y pegar las piezas en la capa de servicios. (Fácil de decir que hecho)

Cuando entro en un código completamente funcional, puedo crear una publicación de blog o un repositorio de github, pero por ahora no lo tengo, así que esto es todo por ahora.