У меня есть довольно стандартный интерфейс хранилища:Repository OO Design - Множественные характеристики
public interface IRepository<TDomainEntity>
where TDomainEntity : DomainEntity, IAggregateRoot
{
TDomainEntity Find(Guid id);
void Add(TDomainEntity entity);
void Update(TDomainEntity entity);
}
Мы можем использовать различные реализации инфраструктуры для того, чтобы обеспечить функциональность по умолчанию (например, Entity Framework, DocumentDb, Таблица хранения и т.д.). Это то, что реализация Entity Framework выглядит (без какого-либо фактического кода EF, для простоты):
public abstract class EntityFrameworkRepository<TDomainEntity, TDataEntity> : IRepository<TDomainEntity>
where TDomainEntity : DomainEntity, IAggregateRoot
where TDataEntity : class, IDataEntity
{
protected IEntityMapper<TDomainEntity, TDataEntity> EntityMapper { get; private set; }
public TDomainEntity Find(Guid id)
{
// Find, map and return entity using Entity Framework
}
public void Add(TDomainEntity item)
{
var entity = EntityMapper.CreateFrom(item);
// Insert entity using Entity Framework
}
public void Update(TDomainEntity item)
{
var entity = EntityMapper.CreateFrom(item);
// Update entity using Entity Framework
}
}
Существует отображение между доменным TDomainEntity
объектом (совокупность) и TDataEntity
Entity Framework объектом данных (таблицами баз данных). Я не буду вдаваться в подробности, почему существуют отдельные объекты домена и данных. Это философия Domain Driven Design (читайте об агрегатах). Здесь важно понять, что репозиторий будет только подвергать объект домена.
Чтобы сделать новое хранилище для, скажем, «пользователей», я мог бы определить интерфейс, как это:
public interface IUserRepository : IRepository<User>
{
// I can add more methods over and above those in IRepository
}
И затем использовать реализацию Entity Framework для обеспечения базовой Find
, Add
и Update
функциональности для заполнителя:
public class UserRepository : EntityFrameworkRepository<Stop, StopEntity>, IUserRepository
{
// I can implement more methods over and above those in IUserRepository
}
Вышеупомянутое решение отлично поработало. Но теперь мы хотим реализовать функциональность удаления. Я предложил следующий интерфейс (который является IRepository
):
public interface IDeleteableRepository<TDomainEntity>
: IRepository<TDomainEntity>
{
void Delete(TDomainEntity item);
}
Класс реализации Entity Framework теперь будет выглядеть примерно так:
public abstract class EntityFrameworkRepository<TDomainEntity, TDataEntity> : IDeleteableRepository<TDomainEntity>
where TDomainEntity : DomainEntity, IAggregateRoot
where TDataEntity : class, IDataEntity, IDeleteableDataEntity
{
protected IEntityMapper<TDomainEntity, TDataEntity> EntityMapper { get; private set; }
// Find(), Add() and Update() ...
public void Delete(TDomainEntity item)
{
var entity = EntityMapper.CreateFrom(item);
entity.IsDeleted = true;
entity.DeletedDate = DateTime.UtcNow;
// Update entity using Entity Framework
// ...
}
}
Как определено выше класса, TDataEntity
родовое прямо сейчас необходимо также иметь тип IDeleteableDataEntity
, который требует следующие свойства:
public interface IDeleteableDataEntity
{
bool IsDeleted { get; set; }
DateTime DeletedDate { get; set; }
}
Эти свойства är e, соответственно, в реализации Delete()
.
Это означает, что, если потребуется, я могу определить IUserRepository
с «удалением» возможностями, которые бы по своей сути позаботиться о соответствующей реализации:
public interface IUserRepository : IDeleteableRepository<User>
{
}
При условии, что соответствующий объект данных Entity Framework является IDeleteableDataEntity
, это не будет проблемой.
Самое замечательное в этой конструкции является то, что я могу начать granualising модель хранилища еще дальше (IUpdateableRepository
, IFindableRepository
, IDeleteableRepository
, IInsertableRepository
) и теперь агрегатные Хранилища могут выставить только соответствующую функциональность как в нашей спецификации (возможно, вам должно быть разрешено для вставки в UserRepository
, но НЕ в ClientRepository
). Кроме того, он определяет стандартизованный способ выполнения определенных действий репозитория (то есть обновление столбцов IsDeleted
и DeletedDate
будет универсальным и не находится в руках разработчика).
ПРОБЛЕМА
Проблема с выше конструкции возникает, когда я хочу создать хранилище для некоторой совокупности БЕЗ возможности удаления, например:
public interface IClientRepository : IRepository<Client>
{
}
EntityFrameworkRepository
реализация все еще требует TDataEntity
быть типа IDeleteableDataEntity
,
Я могу гарантировать, что модель объекта данных клиента реализует IDeleteableDataEntity
, но это вводит в заблуждение и неверно. Появятся дополнительные поля, которые никогда не обновляются. не
Единственное решение, которое я могу думать о том, чтобы удалить IDeleteableDataEntity
общего состояния от TDataEntity
, а затем приводится к соответствующему типу в Delete()
метода:
public abstract class EntityFrameworkRepository<TDomainEntity, TDataEntity> : IDeleteableRepository<TDomainEntity>
where TDomainEntity : DomainEntity, IAggregateRoot
where TDataEntity : class, IDataEntity
{
protected IEntityMapper<TDomainEntity, TDataEntity> EntityMapper { get; private set; }
// Find() and Update() ...
public void Delete(TDomainEntity item)
{
var entity = EntityMapper.CreateFrom(item);
var deleteableEntity = entity as IDeleteableEntity;
if(deleteableEntity != null)
{
deleteableEntity.IsDeleted = true;
deleteableEntity.DeletedDate = DateTime.UtcNow;
entity = deleteableEntity;
}
// Update entity using Entity Framework
// ...
}
}
Поскольку ClientRepository
не реализует IDeleteableRepository
, не будет никакого Delete()
способ выставлен, который хорошо.
ВОПРОС
Может кто-нибудь посоветовать лучшую архитектуру, которая использует C# набрав системы и не включает в Hacky актеров?
Достаточно интересно, я мог бы это сделать, если C# поддерживал множественное наследование (с отдельной конкретной реализацией для поиска, добавления, удаления, обновления).