domain driven design - ddd - ¿Estamos todos buscando el mismo IRepository?
repository pattern java (3)
He estado tratando de encontrar una forma de escribir repositorios genéricos que funcionen en varios almacenes de datos:
public interface IRepository
{
IQueryable<T> GetAll<T>();
void Save<T>(T item);
void Delete<T>(T item);
}
public class MemoryRepository : IRepository {...}
public class SqlRepository : IRepository {...}
Me gustaría trabajar contra las mismas clases de dominio POCO en cada uno. También estoy considerando un enfoque similar, donde cada clase de dominio tiene su propio repositorio:
public interface IRepository<T>
{
IQueryable<T> GetAll();
void Save(T item);
void Delete(T item);
}
public class MemoryCustomerRepository : IRepository {...}
public class SqlCustomerRepository : IRepository {...}
Mis preguntas: 1) ¿Es el primer enfoque incluso posible? 2) ¿Hay alguna ventaja para el segundo enfoque?
El primer enfoque es factible. He hecho algo similar en el pasado cuando escribí mi propio marco de mapeo que apuntaba a RDBMS y
XmlWriter
/XmlReader
. Puede utilizar este tipo de enfoque para facilitar las pruebas unitarias, aunque creo que ahora tenemos herramientas OSS superiores para hacerlo.El segundo enfoque es el que uso actualmente con los creadores de mapas IBATIS.NET . Cada mapeador tiene una interfaz y cada mapeador [podría] proporcionarle sus operaciones CRUD básicas. La ventaja es que cada asignador para una clase de dominio también tiene funciones específicas (como
SelectByLastName
oDeleteFromParent
) expresadas por una interfaz y definidas en el asignador concreto. Debido a esto, no es necesario que implemente repositorios por separado como usted sugiere: nuestros mapeadores concretos se dirigen a la base de datos. Para realizar pruebas unitarias, uso StructureMap y Moq para crear repositorios en memoria que funcionan como lo hace tuMemory*Repository
. Es menos clases para implementar y administrar y menos trabajo en general para un enfoque muy comprobable. Para los datos compartidos en las pruebas unitarias, utilizo un patrón de generador para cada clase de dominio que tiene métodosAsSomeProfile
yAsSomeProfile
(elAsSomeProfile
simplemente devuelve una instancia de generador con datos de prueba preconfigurados).
Aquí hay un ejemplo de lo que suelo terminar en mis pruebas unitarias:
// Moq mocking the concrete PersonMapper through the IPersonMapper interface
var personMock = new Mock<IPersonMapper>(MockBehavior.Strict);
personMock.Expect(pm => pm.Select(It.IsAny<int>())).Returns(
new PersonBuilder().AsMike().Build()
);
// StructureMap''s ObjectFactory
ObjectFactory.Inject(personMock.Object);
// now anywhere in my actual code where an IPersonMapper instance is requested from
// ObjectFactory, Moq will satisfy the requirement and return a Person instance
// set with the PersonBuilder''s Mike profile unit test data
En realidad, existe un consenso general ahora que los repositorios de dominio no deben ser genéricos. Su repositorio debe expresar lo que puede hacer cuando persista o recupere sus entidades.
Algunos repositorios son de solo lectura, algunos son solo de inserción (sin actualización, sin eliminación), algunos tienen solo búsquedas específicas ...
Usando un IQueryable GetAll get, su lógica de consulta se filtrará en su código, posiblemente a la capa de aplicación.
Pero sigue siendo interesante utilizar el tipo de interfaz que proporciona para encapsular objetos Linq Table<T>
para que pueda reemplazarlo con una implementación en memoria para fines de prueba.
Así que sugiero, para llamarlo ITable<T>
, darle la misma interfaz que el objeto linq Table<T>
, y usarlo dentro de los repositorios de dominio específicos (no en lugar de).
A continuación, puede usar sus repositorios específicos en la memoria utilizando una implementación ITable<T>
en la memoria.
La forma más sencilla de implementar ITable<T>
en la memoria es utilizar una List<T>
y obtener una interfaz IQueryable IQueryable<T>
utilizando el método de extensión .AsQueryable ().
public class InMemoryTable<T> : ITable<T>
{
private List<T> list;
private IQueryable<T> queryable;
public InMemoryTable<T>(List<T> list)
{
this.list = list;
this.queryable = list.AsQueryable();
}
public void Add(T entity) { list.Add(entity); }
public void Remove(T entity) { list.Remove(entity); }
public IEnumerator<T> GetEnumerator() { return list.GetEnumerator(); }
public Type ElementType { get { return queryable.ElementType; } }
public IQueryProvider Provider { get { return queryable.Provider; } }
...
}
Puede trabajar de forma aislada en la base de datos para realizar pruebas, pero con verdaderos repositorios específicos que ofrecen más información sobre el dominio.
Esto es un poco tarde ... pero eche un vistazo a la implementación de IRepository en CommonLibrary.NET en codeplex. Tiene un conjunto de características bastante bueno.
En cuanto a su problema, veo mucha gente que usa métodos como GetAllProducts (), GetAllEmployees () en su implementación de repositorio. Esto es redundante y no permite que su repositorio sea genérico. Todo lo que necesita es GetAll () o All (). La solución proporcionada anteriormente resuelve el problema de nombres.
Esto se toma de la documentación de CommonLibrary.NET en línea:
0.9.4 Beta 2 tiene una potente implementación de repositorio.
* Supports all CRUD methods ( Create, Retrieve, Update, Delete )
* Supports aggregate methods Min, Max, Sum, Avg, Count
* Supports Find methods using ICriteria<T>
* Supports Distinct, and GroupBy
* Supports interface IRepository<T> so you can use an In-Memory table for unit-testing
* Supports versioning of your entities
* Supports paging, eg. Get(page, pageSize)
* Supports audit fields ( CreateUser, CreatedDate, UpdateDate etc )
* Supports the use of Mapper<T> so you can map any table record to some entity
* Supports creating entities only if it isn''t there already, by checking for field values.