0

У меня есть довольно стандартный интерфейс хранилища: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# поддерживал множественное наследование (с отдельной конкретной реализацией для поиска, добавления, удаления, обновления).

ответ

1

Я действительно думаю, что вы усложняете что-то слишком много, пытаясь получить самое общее решение для всех, однако я думаю, что есть довольно простое решение вашей текущей проблемы.

TDataEntity представляет собой структуру данныхнастойчивости, это не имеет никакого значения домена, и это не известно за пределами сохранения слоя. Таким образом, он может иметь поля, которые он никогда не будет использовать, репозиторий является единственным, кто знает, что это постоянство подробно. Вы можете позволить себе быть «неряшливым» здесь, на этом уровне все не так важно.

Даже «храбрый» литой является хорошим решением, потому что он находится в одном месте и частная деталь.

Полезно иметь чистый и обслуживаемый код везде, однако мы не можем позволить себе тратить время на создание «идеальных» решений на каждом уровне. Лично для моделей просмотра и настойчивости я предпочитаю самые быстрые и простые решения, даже если они немного вонючие.

P.S: В качестве правила большого пальца общие интерфейсы репозитория хороши, общие абстрактные репозитории не так много (вам нужно быть осторожными), если вы не сериализуете вещи или не используете doc db.

 Смежные вопросы

  • Нет связанных вопросов^_^