¿Cómo utilizar correctamente el patrón de repositorio?

86

Me pregunto cómo debería agrupar mis repositorios. Como en los ejemplos que vi en asp.net mvc y en mis libros, básicamente usan un repositorio por tabla de base de datos. Pero eso parece una gran cantidad de repositorios que lo llevan a tener que llamar a muchos repositorios más adelante para burlarse y esas cosas.

Así que supongo que debería agruparlos. Sin embargo, no estoy seguro de cómo agruparlos.

En este momento hice un repositorio de registro para manejar todas mis cosas de registro. Sin embargo, hay como 4 tablas que necesito actualizar y antes tenía 3 repositorios para hacer esto.

Por ejemplo, una de las tablas es una tabla de licencias. Cuando se registran, miro su clave y la reviso para ver si existe en la base de datos. Ahora, ¿qué sucede si necesito verificar esta clave de licencia o algo más de esa tabla en algún otro lugar que no sea el registro?

Un lugar podría ser el inicio de sesión (verifique si la clave no está vencida).

Entonces, ¿qué haría yo en esta situación? ¿Reescribe el código nuevamente (rompe DRY)? Intente combinar estos 2 repositorios juntos y espere que ninguno de los métodos sea necesario en algún otro momento (como si pudiera tener un método que verifique si se usa userName, tal vez lo necesite en otro lugar).

Además, si los fusiono, necesitaría 2 capas de servicio que vayan al mismo repositorio, ya que creo que tener toda la lógica para 2 partes diferentes de un sitio sería largo y tendría que tener nombres como ValidateLogin (), ValdiateRegistrationForm () , ValdiateLoginRetrievePassword () y etc.

¿O llamar al Repositorio de todos modos y tener un nombre extraño?

Parece difícil crear un repositorio que tenga un nombre lo suficientemente general para que pueda usarlo en muchos puntos de su aplicación y aún así tenga sentido, y no creo que llamar a otro repositorio en un repositorio sea una buena práctica.

chobo2
fuente
11
+1. Gran pregunta.
Griegs
Gracias, me ha estado molestando desde hace algún tiempo. Dado que encuentro que los que vi en el libro son demasiado simples, no le muestran qué hacer en estas situaciones.
chobo2
Básicamente estoy haciendo lo mismo que tú y sí, si tengo una clase linq2sql que se usa en más de 1 repositorio y necesito cambiar la estructura de la tabla, rompo DRY. Menos que ideal. Ahora lo planeo un poco mejor, así que no necesito usar una clase linq2sql más de una vez, lo que supongo que es una buena separación de preocupaciones, pero preveo un día en el que esto será un problema real para mí.
Griegs
Hice una pregunta similar (pero no idéntica) aquí: stackoverflow.com/questions/910156/…
Grokys
Sí, pero parece difícil planificarlo para todas las situaciones. Como dije, puedo fusionar tal vez el inicio de sesión y el registro en una autenticación y tener 2 capas separadas. Eso probablemente resolverá mi problema. Pero, ¿qué pasa si en la página de perfil de mi sitio quiero mostrarles su clave por cualquier motivo (tal vez dejaré que htem la cambie o algo así)? Ahora, ¿qué hago para romper DRY y escribir lo mismo? O intente crear un repositorio que de alguna manera pueda encajar las 3 tablas con un buen nombre.
chobo2

Respuestas:

41

Una cosa que hice mal cuando jugué con el patrón del repositorio: al igual que tú, pensé que la tabla se relaciona con el repositorio 1: 1. Cuando aplicamos algunas reglas de Domain Driven Design, el problema de agrupación de repositorios suele desaparecer.

El repositorio debe ser por raíz agregada y no por tabla. Significa que, si la entidad no debería vivir sola (es decir, si tiene una Registrantque participa en particular Registration), es solo una entidad, no necesita un repositorio, debería actualizarse / crearse / recuperarse a través del repositorio de raíz agregada. pertenece.

Por supuesto, en muchos casos, esta técnica de reducir el recuento de repositorios (en realidad, es más una técnica para estructurar su modelo de dominio) no se puede aplicar porque se supone que cada entidad es una raíz agregada (que depende en gran medida de su dominio, Solo puedo proporcionar conjeturas a ciegas). En su ejemplo, Licenseparece ser una raíz agregada porque necesita poder verificarlos sin contexto de Registrationentidad.

Pero eso no nos limita a los repositorios en cascada (el Registrationrepositorio puede hacer referencia al Licenserepositorio si es necesario). Eso no nos limita a hacer referencia al Licenserepositorio (preferiblemente, a través de IoC) directamente desde el Registrationobjeto.

Simplemente trate de no conducir su diseño a través de complicaciones proporcionadas por tecnologías o malentendidos. Agrupar los repositorios ServiceXsolo porque no desea construir 2 repositorios no es una buena idea.

Mucho mejor sería darle un nombre propio, RegistrationServicees decir

Pero los servicios deben evitarse en general; a menudo son una causa que conduce a un modelo de dominio anémico .

EDITAR:
comience a usar IoC. Realmente alivia el dolor de inyectar dependencias.
En lugar de escribir:

var registrationService = new RegistrationService(new RegistrationRepository(),  
      new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());

podrás escribir:

var registrationService = IoC.Resolve<IRegistrationService>();

Ps. Sería mejor usar el llamado localizador de servicios comunes, pero eso es solo un ejemplo.

Arnis Lapsa
fuente
17
Jajaja ... Te estoy dando un consejo para usar el localizador de servicios. Siempre me alegro de ver lo tonto que he sido en el pasado.
Arnis Lapsa
1
@Arnis Parece que sus opiniones han cambiado. Me interesa ¿cómo respondería esta pregunta de manera diferente ahora?
ngm
10
@ngm ha cambiado mucho desde que respondí esto. Todavía estoy de acuerdo en que la raíz agregada debe trazar límites transaccionales (guardarse como un todo), pero soy mucho menos optimista acerca de abstraer la persistencia usando el patrón de repositorio. Últimamente, solo estoy usando ORM directamente, porque cosas como la administración de carga ansiosa / perezosa son demasiado incómodas. Es mucho más beneficioso centrarse en desarrollar un modelo de dominio rico en lugar de centrarse en abstraer la persistencia.
Arnis Lapsa
2
@ Desarrollador no, no exactamente. todavía deben ser persistentes ignorantes. recupera la raíz agregada del exterior y llama al método que hace el trabajo. mi modelo de dominio tiene cero referencias, solo las estándar del marco .net. para lograrlo, debe tener un modelo de dominio rico y herramientas que sean lo suficientemente inteligentes (NHibernate hace el truco).
Arnis Lapsa
1
De lo contrario. Obtener más de un ggregate a menudo significa que puede usar PK o índices. Mucho más rápido que aprovechando las relaciones que genera EF. Los agregados de raíz más pequeños, más fácil de obtener rendimiento en ellos.
jgauffin
5

Una cosa que comencé a hacer para abordar esto es desarrollar servicios que envuelvan N repositorios. Con suerte, sus marcos de DI o IoC pueden ayudar a facilitarlo.

public class ServiceImpl {
    public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}

¿Tiene sentido? Además, entiendo que hablar de servicios en esta mansión puede o no cumplir con los principios de DDD, solo lo hago porque parece funcionar.

neouser99
fuente
1
No, eso no tiene mucho sentido. No uso marcos DI o IoC en este momento porque tengo suficiente en mi plato.
chobo2
1
Si puede crear una instancia de sus repositorios con solo un nuevo (), puede probar esto ... public ServiceImpl (): este (nuevo Repo1, nuevo Repo2 ...) {} como un constructor adicional en el servicio.
neouser99
¿Inyección de dependencia? Ya lo hago, pero todavía no estoy seguro de cuál es su código y qué resuelve.
chobo2
Me dirijo específicamente a la fusión de repositorios. ¿Qué código no tiene sentido? Si está usando DI, entonces el código en la respuesta funcionará en el sentido de que su marco DI inyectará esos servicios IRepo, en el código de comentario es básicamente una pequeña solución para hacer DI (básicamente su constructor sin parámetros 'inyecta' esas dependencias en su ServiceImpl).
neouser99
Ya uso DI para poder realizar una prueba unitaria mejor. Mi problema es que si crea sus capas de servicio y repositorios con nombres detallados. Entonces, si alguna vez necesita usarlos en cualquier otro lugar, se verán llamadas extrañas como la capa RegistrationService que llama al RegRepo en alguna otra clase, como ProfileClass. Así que no veo en tu ejemplo lo que estás haciendo plenamente. Me gusta Si comienza a tener demasiados repositorios en la misma capa de servicio, tendrá una lógica de negocios y una lógica de validación muy diferentes. Ya que en la capa de servicio normalmente pones en lógica de validación. Tantos, necesito más ...
chobo2
2

Lo que estoy haciendo es que tengo una clase base abstracta definida de la siguiente manera:

public abstract class ReadOnlyRepository<T,V>
{
     V Find(T lookupKey);
}

public abstract class InsertRepository<T>
{
     void Add(T entityToSave);
}

public abstract class UpdateRepository<T,V>
{
     V Update(T entityToUpdate);
}

public abstract class DeleteRepository<T>
{
     void Delete(T entityToDelete);
}

Luego, puede derivar su repositorio de la clase base abstracta y extender su repositorio único siempre que los argumentos genéricos difieran, por ejemplo;

public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
                                     ReadOnlyRepository<string, IRegistrationItem> 

etc ....

Necesito los repositorios separados porque tenemos restricciones en algunos de nuestros repositorios y esto nos da la máxima flexibilidad. Espero que esto ayude.

Michael Mann
fuente
¿Estás tratando de hacer un repositorio genérico para lidiar con todo esto?
chobo2
Entonces, ¿qué pasa realmente con el método de actualización? Como si tuviera esta actualización de V y pasa una entidad T a la actualización, pero no hay un código que la actualice, ¿o sí?
Chobo2
Sí. Habrá código en el método de actualización porque escribirás una clase que descienda del repositorio genérico. El código de implementación puede ser Linq2SQL o ADO.NET o lo que haya elegido como tecnología de implementación de acceso a datos
Michael Mann
2

Tengo esto como mi clase de repositorio y sí, lo extiendo en el repositorio de tabla / área, pero a veces tengo que romper DRY.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvcRepository
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected System.Data.Linq.DataContext _dataContextFactory;

        public IQueryable<T> All()
        {
            return GetTable.AsQueryable();
        }

        public IQueryable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where<T>(exp).AsQueryable();
        }

        public T Single(Func<T, bool> exp)
        {
            return GetTable.Single(exp);
        }

        public virtual void MarkForDeletion(T entity)
        {
            _dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
        }

        public virtual T CreateInstance()
        {
            T entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        public void SaveAll()
        {
            _dataContextFactory.SubmitChanges();
        }

        public Repository(System.Data.Linq.DataContext dataContextFactory)
        {
            _dataContextFactory = dataContextFactory;
        }

        public System.Data.Linq.Table<T> GetTable
        {
            get { return _dataContextFactory.GetTable<T>(); }
        }

    }
}

EDITAR

public class AdminRepository<T> : Repository<T> where T: class
{
    static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);

    public AdminRepository()
        : base( dc )
    {
    }

También tengo un contexto de datos que se creó utilizando la clase Linq2SQL.dbml.

Así que ahora tengo un repositorio estándar que implementa llamadas estándar como All y Find y en mi AdminRepository tengo llamadas específicas.

No responde a la pregunta de DRY aunque no lo creo.

gritos
fuente
¿Para qué "Repositorio"? y como CreateInstance? No estoy seguro de qué está haciendo todo lo que tienes.
chobo2
Es genérico como cualquier cosa. Básicamente, necesita tener un repositorio específico para su (área). Consulte la edición anterior para mi AdminRepository.
Griegs
1

Aquí hay un ejemplo de una implementación de Repositorio genérico usando FluentNHibernate. Es capaz de conservar cualquier clase para la que haya escrito un asignador. Incluso es capaz de generar su base de datos basada en las clases del asignador.

James Jones
fuente
1

El patrón de repositorio es un patrón de diseño incorrecto. Trabajo con muchos proyectos .Net antiguos y este patrón suele provocar errores de "Transacciones distribuidas", "Reversión parcial" y "Grupo de conexiones agotado" que podrían evitarse. El problema es que el patrón intenta manejar conexiones y transacciones internamente, pero deben manejarse en la capa del controlador. Además, EntityFramework ya abstrae gran parte de la lógica. Sugeriría usar el patrón de servicio en su lugar para reutilizar el código compartido.

ColacX
fuente
0

Te sugiero que mires Sharp Architecture . Sugieren usar un repositorio por entidad. Lo estoy usando actualmente en mi proyecto y estoy satisfecho con los resultados.

Astuto
fuente
Daría un +1 aquí, pero ha indicado que los contenedores DI o IoC no son una opción (dado que esos no son los únicos beneficios de Sharp Arch). Supongo que hay algo de código existente en el que está trabajando.
neouser99
¿Qué se considera una entidad? ¿Esa es toda la base de datos? ¿O es una tabla de base de datos? Los contenedores DI o IoC no son una opción en este momento, ya que no quiero aprender eso además de las otras 10 cosas que estoy aprendiendo al mismo tiempo. son algo que analizaré en mi próxima revisión de mi sitio o en mi próximo proyecto. Aunque no estoy seguro de si será este, un vistazo rápido al sitio y parece querer que use nhirbrate y estoy usando linq to sql en este momento.
Chobo2