Arquitectura Onion-Repositorio Vs Servicio?


Estoy aprendiendo la conocida Arquitectura Onion de Jeffrey Palermo. No es específico de este patrón, pero no puedo ver claramente la separación entre repositorios y servicios de dominio. I (mis)entender que el repositorio se refiere al acceso a los datos y el servicio son más acerca de la capa de negocio (referencia de uno o más repositorios).

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

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

Author: Cuong Le, 2012-09-07

5 answers

El repositorio no es una puerta de enlace para acceder a la base de datos. Es una abstracción que le permite almacenar y cargar objetos de dominio desde algún tipo de almacén de persistencia. (Base de datos, Caché o incluso Colección simple). Toma o devuelve los objetos de dominio en lugar de su campo interno, por lo tanto, 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 como su uso aumento del recuento de campos caso/ objeto. 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 de repository pattern 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 de repositorio en el modelado de dominios con gran detalle. Puede que te interese.

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

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 a los llamantes de los servicios para usar los repositorios directamente.

 30
Author: nwang0,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-18 12:12:18

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

Por lo tanto, los repositorios deben proporcionar una interfaz para la operación CRUD en las entidades de dominio. Recuerde 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 Order y OrderLines:

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

La lógica de negocio debe estar en las Entidades de Dominio, no en la capa de repositorio , la lógica de la aplicación debe estar en la capa de servicio como su mención, los servicios aquí juegan un papel como coordinador entre repositorios.

 15
Author: Cuong Le,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-07 12:59:56

Creo que el Repositorio debería ser solo para operaciones CRUD.

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

Así que IRepository tendría: Add, Remove, Update, Get, getAll y posiblemente una versión de cada uno de los que toma una lista, es decir, AddMany, RemoveMany, 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 Find(criteria) que toma criterios. O puedes ir con cosas como IPersonFinder que definiría funciones personalizadas como: un 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. Vas a usar un lenguaje de consulta de algún tipo,o una asociación clave-valor más simple, etc. La potencia total de la interfaz también es más difícil de entender simplemente mirándola. También es más fácil filtrar implementaciones con este método, porque el los criterios podrían basarse en un tipo particular de sistema de persistencia, como si se toma una consulta SQL como criterio, por ejemplo. Por otro lado, podría evitar que tenga que volver continuamente al IFinder porque ha golpeado 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 podría decidir mezclar ambos juntos, y tener un IFinder definir un método Find, e IMyObjectFinders que implementan IFinder, pero también agregan métodos personalizados como findByName.

El servicio actúa como supervisor. Supongamos que necesita recuperar un elemento, pero también debe procesarlo antes de devolverlo al cliente, y ese procesamiento puede requerir información encontrada en otros elementos. Por lo tanto, el servicio recuperaría todos los elementos apropiados utilizando los repositorios y los Buscadores, y luego enviaría el elemento para ser procesado a objetos que encapsulan la lógica de procesamiento necesaria, y finalmente devolvería el artículo solicitado por el cliente. En algún momento, no se requerirá procesamiento ni recuperaciones adicionales, en tales casos, no necesita tener un servicio. Puede hacer que los clientes llamen directamente a los repositorios y a los Buscadores. Esta es una diferencia con la Cebolla y una arquitectura de Capas, en la Cebolla, todo lo que es más exterior puede acceder a todo lo más interior, no solo a la capa anterior.

Sería el papel de la repositorio para cargar la jerarquía completa de lo que se necesita para construir correctamente el elemento que devuelve. Así que si su repositorio devuelve un elemento que tiene una Lista de otro tipo de elemento, ya debería resolver. Personalmente, sin embargo, me gusta diseñar mis objetos para que no contengan referencias a otros elementos, porque hace que el repositorio sea más complejo. Prefiero que mis objetos mantengan el Id de otros elementos, de modo que si el cliente realmente necesita ese otro elemento, pueda consultarlo nuevamente 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 lo necesita.

Podría, si realmente sintió la necesidad, agregar un mecanismo de restricción a su Repositorio, de modo que pueda especificar exactamente qué campo del elemento necesita. Digamos que tiene una Persona, y solo cuida su nombre, podría hacer Get (id, nombre) y el Repositorio no se molestaría en obtener todos los campos de la Persona, solo su campo de nombre. Hacer sin embargo, esto añade una complejidad considerable al repositorio. Y hacer esto con objetos jerárquicos es aún más complejo, especialmente si desea restringir campos dentro de campos de 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.

 6
Author: Didier A.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-17 20:15:20

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 ahora, todo bien. Necesitamos declarar 'algo' que crea el modelo de vista. Vamos a simplificar y decir:

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

Bien, ¿qué son las dependencias ? Nosotros tal vez necesitamos árbol de categoría, los productos, la página, cuántos productos totales, etc.

Así que si implementamos esto en un repositorio, esto podría verse más o menos como esto:

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 su lugar, de vuelta a los 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é hay exactamente dentro 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 Repositorios, así 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 'consulta' sintaxis en lugar de la sintaxis del método, pero en lugar de implementar nuestro propio complejo, podemos usar nuestro OR. Además, tenemos acceso a TODOS los repositorios, por lo que todavía podemos hacer nuestra Unidad de trabajo dentro de nuestro servicio.

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

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

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 al ViewModel, recuerde que esto SOLO asignará datos a valores, si está haciendo algo más aquí, probablemente 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 imaginar el CategoryProductViewModel por ti mismo en este momento.

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

Estamos haciendo visitas de 3 db, y estamos obteniendo todos los campos de categoría debido al Hallazgo. También la carga lenta debe estar habilitada. No es una solución real, ¿verdad ?

Para mejorar esto, podemos cambiar find con where... pero esto delegará el Single o Find al ViewModel, también devolverá un IQueryable<Category>, donde sabemos que debería ser exactamente uno.

Recuerdas que dije "Todavía estoy luchando?"esto es principalmente por qué. Para solucionar esto, debemos devolver los datos necesarios exactos del servicio (también conocido como el ..... lo sabes .... sí! the ViewModel).

Así que vamos a de vuelta 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 las ensamblan como ViewModel.

Esto es malo, ahora mis servicios conocen viewmodels... arreglemos eso.

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

ICategoryWithProductsAndChildsIds // quite verbose, i know.

Bueno, ahora solo tenemos que declarar nuestro ViewModel como {[23]]}

public class CategoryViewModel : ICategoryWithProductsAndChildsIds 

E implementarlo de la manera que querer.

La interfaz parece que tiene demasiadas cosas, por supuesto que se puede dividir con ICategoryBasic, IProducts, IChilds, o como quieras llamarlos.

Así que cuando implementamos otro ViewModel, podemos elegir hacer solo IProducts. Podemos hacer que nuestros servicios tengan métodos (privados o no) para recuperar esos contratos y pegar las piezas en la capa de servicio. (Fácil de decir que de hacer)

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

 6
Author: Bart Calixto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-10 20:26:21

En el Diseño Impulsado por Dominio, el repositorio es responsable de recuperar todo el Agregado.

 4
Author: Dennis Traub,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-07 11:28:40