Repository and Specification pattern

I'm currently setting up a new project, and I have run into a few things, where I need a little input.

This is what i'm considering:

  • I would like a generic repository

  • I don't want to return IQueryable from my repository.

  • I would like to encapsulate my queries in specifications.

  • I have implemented the specification pattern

  • It needs to be easily testable

Now this is where I get a little stuck and my question is which way would be the most elegant way of calling the find method with one or more specifications:

(Fluent): bannerRepository.Find().IsAvailableForFrontend().IsSmallMediaBanner()

или выражать запросы как лямбды с моими спецификациями

(Лямбда): bannerRepository.Find.Where(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner)

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

Чего я надеюсь достичь, так это сохранить некоторую гибкость в отношении возможности сочетания спецификаций и предоставления опыта "фильтрации" со спецификациями, но без утечки IQueryable для контроллера, но, скорее, для ISpecifiable, который позволяет только изменить запрос со спецификациями, а не с Linq. Но я только что вернулся к утечке логики запросов в контроллер таким образом?

3 ответа

Решение

Я видел некоторые Fluent API, которые используют свойства для спецификаций, поэтому они не добавляют шум круглых скобок для клиентов.

bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()

Быть Exec() метод для выполнения спецификаций в отношении репо.

но даже если вы не используете свойства, я бы пошел на свободный API, так как он имеет минимальный шум.

или может быть каким-то совершенно другим способом?

Ну, на самом деле я не получаю именно вашу реализацию репозитория (например, что будет метод .Find() вернуть?), но я бы выбрал другое направление:

public class Foo 
{ 
    public Int32 Seed { get; set; }
}

public interface ISpecification<T> 
{
    bool IsSatisfiedBy(T item);
}

public interface IFooSpecification : ISpecification<Foo> 
{
    T Accept<T>(IFooSpecificationVisitor<T> visitor);
}

public class SeedGreaterThanSpecification : IFooSpecification
{
    public SeedGreaterThanSpecification(int threshold)
    {
        this.Threshold = threshold;
    }
    public Int32 Threshold { get; private set; }
    public bool IsSatisfiedBy(Foo item) 
    {
        return item.Seed > this.Threshold ;
    }
    public T Accept<T>(IFooSpecificationVisitor<T> visitor)
    {
        return visitor.Visit(this);
    }
}
public interface IFooSpecificationVisitor<T>
{
    T Visit(SeedGreaterThanSpecification acceptor);
    T Visit(SomeOtherKindOfSpecification acceptor);
    ...
}
public interface IFooRepository 
{
    IEnumerable<Foo> Select(IFooSpecification specification);
}
public interface ISqlFooSpecificationVisitor : IFooSpecificationVisitor<String> { }
public class SqlFooSpecificationVisitor : ISqlFooSpecificationVisitor
{
    public string Visit(SeedGreaterThanSpecification acceptor)
    {
        return "Seed > " + acceptor.Threshold.ToString();
    }
    ...
}
public class FooRepository
{   
    private ISqlFooSpecificationVisitor visitor;

    public FooRepository(ISqlFooSpecificationVisitor visitor)
    {
        this.visitor = visitor;
    }

    public IEnumerable<Foo> Select(IFooSpecification specification)
    {
        string sql = "SELECT * FROM Foo WHERE " + specification.Accept(this.visitor);
        return this.DoSelect(sql);
    }

    private IEnumerable<Foo> DoSelect(string sql)
    {
        //perform the actual selection;
    }
}

Итак, у меня есть объект, его интерфейс спецификации и несколько разработчиков, участвующих в шаблоне посетителя, его интерфейс репозитория, принимающий интерфейс спецификации, и его реализация репозитория, принимающая посетителя, способного переводить спецификации в предложения SQL (но это только вопрос этого случая, конечно). Наконец, я бы составил спецификацию "за пределами" интерфейса репозитория (используя свободный интерфейс).

Может быть, это просто наивная идея, но я нахожу ее довольно простой. Надеюсь это поможет.

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

Учитывая следующее:

bannerRepository.Find.Where(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner)

Я не знаю, как выглядит ваш шаблон, но вы могли бы изменить некоторые вещи здесь:

Создайте универсальный интерфейс с именем "IRepository" типа, содержащий все методы для доступа к данным.

Это может выглядеть так:

interface IRepository<T> where T : class
{
    IEnumerable<T> FindAll(Func<T, bool> exp);

    T FindSingle(Func<T, bool> exp);
}   

Создайте абстрактный класс 'Repository', реализующий этот интерфейс:

class Repository<T> : IRepository<T> where T : class
{
    TestDataContext _dataContext = TestDataContext();

    public IEnumerable<T> FindAll(Func<T, bool> exp)
    {
        _dataContext.GetTable<T>().Where<T>(exp);
    }

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

Теперь мы можем создать интерфейс для таблицы / объектов баннеров, который реализует наш 'IRepository' и конкретный класс, расширяющий абстрактный класс 'Repository' и реализующий 'IBannerInterface':

interface IBannerRepository : IRepository<Banner>
{
}

И соответствующий репозиторий для его реализации:

class BannerRepository : Repository<Banner>, IBannerRepository
{
}

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

Вызов этих методов будет таким простым:

BannerRepository _repo = new BannerRepository();

_repo.FindSingle(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner);

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

Надеюсь, поможет!

Другие вопросы по тегам