В статье Проблемный шаблон Repository и судя по комментариям многим не понравилась та часть, где объекты *Query скрываются за IQueryFactory. С первого взгляда кажется, что QueryFactory превращается в очередной god-object.
Я уже отвечал, что это не так, потому что в самой QueryFactory нет логики и она только создает объекты запросы. Теперь я могу дать ссылки на две статьи, где от QueryFactory остался только интерфейс, который реализуется с помощью IoC.
Первая статья моего коллеги Тимура Рахматиллаева совсем недавно выложена на Хабре Получение экземпляра класса запроса по сигнатуре его интерфейса.
Вторая статья моего бывшего коллеги Дмитрия Крючкова была выложена в его блоге Alternative to repository pattern: query objects using Castle Windsor с примеры кода на GitHub'е
Советую изучить обе статьи, довольно интересные подходы. С первого взгляда могут показаться слишком сложными и абстрактными, но если их попробовать, то преимущества становятся очевидны.
Преимущества всегда очевидны. Дьявол прячется в недостатках.
ОтветитьУдалитьАлександр, можно вопрос именно к тебе, как к пустившему волну? Ты же придерживаешься принципа SRP при проектировании? Можешь сформулировать responsibility класса LoginCriterion в примере Тимура?
А мне вообще непонятен ажиотаж вокруг этого ServiceLocator-подобного IQueryFactory. Насколько я понимаю, вещь сугубо опциональная. Предпочитаешь явно объявлять зависимости - прокидывай Query напрямую, не любишь прокидывать много зависимостей - прокидывай IQueryFactory, хотя это 1. практически прокидывание IoC контейнера 2. сделает стаббинг/мокинг зависимостей менее интуитивным
ОтветитьУдалитьВ любом случае, IQueryFactory никак не влияет на достоинства/недостатки Query - он является надстройкой (пусть, на мой взгляд, и сомнительной) над ним.
@Pasha
ОтветитьУдалитьЭто можно сказать контекст запроса, а фактически DTO для передачи критериев запроса.
@Idsa
ОтветитьУдалитьЕще есть один нюанс - уменьшается количество кода. Опять же на любителя.
Александр, ты о каком коде? О тех трех строчках на каждую зависимость:
ОтветитьУдалитьprivat readonly IQuery1 _query1;
public SomeType(IQuery1 query1)
{
_query1 = query1;
}
Да, неприятный момент. Меня напрягает, когда прокидывание зависимостей - портянка на полэкрана. Кажется, Мартин писал, что если больше 3-4 зависимостей, то это уже говнокод, и его нужно рефакторить. Но рефактиронг в сторону IQueryFactory - еще больший говнокод (пусть и удобный). Хм...
@Idsa
ОтветитьУдалитьЭто откуда?
@Александр
ОтветитьУдалитьЧто именно?
@Idsa
ОтветитьУдалитьВот это откуда:
> О тех трех строчках на каждую зависимость:
privat readonly IQuery1 _query1;
public SomeType(IQuery1 query1)
{
_query1 = query1;
}
@Александр
ОтветитьУдалитьЭто просто пример пробрасывания непосредственно Query, а не QueryFactory
@Idsa
ОтветитьУдалитьТак не получится, потому что IQuery* будет очень много.
@Александр
ОтветитьУдалитьИх будет столько же, сколько и методов в IQueryFactory.
@Idsa
ОтветитьУдалитьВ ссылках, которые я привел у IQueryFactory есть только один метод не зависимо от кол-ва объектов Query*
Ну ок. Если отталкиваться от статей, то будет прокидываться не IQuery1, а IQuery<SomeCriterion, SomeResult>.
ОтветитьУдалить@Idsa
ОтветитьУдалитьЯ не понимаю куда это будет прокидываться :)
Контроллеры, например, будут принимать только IQueryFactory, как и все остальные объекты в системе.
@Александр
ОтветитьУдалитьТак я рассматриваю ситуацию, когда QueryFactory нет
@Idsa
ОтветитьУдалитьА, когда ее нет слишком много инжектировать придется. Для однотипных объектов всё-таки надо использовать фабрику.
@Александр
ОтветитьУдалитьВ общем случае ты, наверное, прав. Но так ли много Query будет в одном сервисе/контроллере? 2, 3, 5? Насколько для нас важно явно видеть, от каких запросов зависит сервис (тестировать такие классы все же удобнее)? От ответов на эти вопросы, думаю, зависело бы мое решение, если бы я вдруг собрался переползти с IQueryable+Specifications на Queries.
@Idsa
ОтветитьУдалитьДопустим в начала их будет по 3-4 в каждом контроллере, а потом по 10-12. Будешь рефакторить?
Очевидно же, что кол-во запросов увеличивается с ростом проекта.
>>Будешь рефакторить?
ОтветитьУдалитьСкорее всего, да
>>Очевидно же, что кол-во запросов увеличивается с ростом проекта.
Количество запросов в целом - да, увеличивается. Количество запросов в конкретном сервисе/контроллере - не факт. По крайней мере их увеличение (как и любых других зависимостей) - отличной повод задуматься о рефакторинге.
@Idsa
ОтветитьУдалитьЯ за фабрику с самого начала :)
@Александр
ОтветитьУдалитьПожалуй, ты меня убедил :)
Уважаемый Александр и коллеги!
ОтветитьУдалитьВ ответ на публикацию "Проблемный шаблон Repository" хочу дать свой ответ
с противоположным названием "Безпроблемный шаблон Repository".
Приведенный ниже пример слоя Persistance / Repository лишь частичной мною
подкорректирован. Все основные идеи взяты из статьи
http://weblogs.asp.net/shijuvarghese/archive/2011/01/06/developing-web-apps-using-asp-net-mvc-3-razor-and-ef-code-first-part-1.aspx
Но само решение настолько простое, логичное и универсальное, что не могу его не привести
после чтения статьи Александра.
Слой Persistance состоит из 2х подслоёв - фабрики баз данных и репозитория данных
// базовый интерфейс для подслоя фабрики баз данных
public interface IDatabaseFactory
{
DataContext Get();
}
// интерфейс для работы с базой данных Rpr
public interface IRprDatabaseFactory : IDatabaseFactory
{
}
///
/// Класс который фактически является обёрткой над DataContext нужной нам бд Rpr
///
public class RprDatabaseFactory : IRprDatabaseFactory, IDisposable
{
/// Контекст конкретной бд
private RprDataContext context;
/// Получение контекста бд
public DataContext Get()
{
if (context == null)
{
context = new RprDataContext();
}
return context;
}
public void Dispose()
{
if (context != null)
context.Dispose();
}
}
// базовый интерфейс для подслоя репозитория
public interface IRepository where T : class
{
void Add(T entity);
void Delete(T entity);
T Get(Expression> where);
IEnumerable GetAll();
IEnumerable GetMany(Expression> where);
}
// базовый класс для подслоя репозитория
public class BaseRepository : IRepository where TEntity : class
{
/// Контекст бд
protected DataContext context;
/// таблицы с сущностями бд
protected Table table;
public BaseRepository(IDatabaseFactory dbFactory)
{
context = dbFactory.Get();
table = context.GetTable();
}
public virtual void Add(TEntity entity)
{
if (entity != null)
{
table.InsertOnSubmit(entity);
}
}
public virtual void Delete(TEntity entity)
{
if (entity != null)
{
table.DeleteOnSubmit(entity);
}
}
public virtual TEntity Get(Expression> where)
{
return table.Where(where).FirstOrDefault();
}
public virtual IEnumerable GetMany(Expression> where)
{
return table.Where(where);
}
public virtual IEnumerable GetAll()
{
return table;
}
}
// репозиторий объектов бд Rpr
public class RprRepository : BaseRepository where TEntity : class
{
///
/// При помощи механизма фреймворка IoC получаем экземпляр этого класса
/// (а в него вложен экземпляр DataContext нужной нам бд) с необходимым временем жизни
/// Например со временем жизна Http запроса
///
public RprRepository() : base(UnityInstance.Container.Resolve())
{
}
public RprDataContext Context
{
get
{
var rprContext = (RprDataContext) context;
return rprContext;
}
}
}
// создаём репозиторий для конкретного типа сущности модели домена
public class ProjectRepository : RprRepository
{
}
// использование методов репозитория в слое бизнес-логики
var repository = new ProjectRepository();
var list = repository.GetMany(item => item.parent_project_id == ProjectId).ToList();
Алексей, вы пропустили всё, о чем говорилось в
ОтветитьУдалитьhttp://blog.byndyu.ru/2011/08/repository.html и комментариях к этой статье.
Прежде, чем вы перечитаете статью и комментарии могу сразу сказать пару вещей.
В вашем решение IDatabaseFactory возвращает DataContext, а это значит, что работа с данными будет раз и навсегда привязана к EF. Что вы будете делать, если надо будет менять ORM или часть объектов хранить не в БД?
Если надо сделать сложную выборку, которая будет не просто Where, а еще сортировку, join т.п., в какой метод репозитория вы это передадите?
Если вам нужно сделать подгрузку связных сущностей (Fetch), где вы будете это указывать?
Описанный вами подход можно назвать Generic Repository со всеми его известными проблемами. Он подходит, если у вас маленькие проекты, которые никогда не надо поддерживать, либо если у вас большие проекты, которые можно делать бесконечно долго. Это не гибкое решение, которое ведет к техническим долгам.
Почему то сбивается разметка в приведенном выше коде - должно быть так
ОтветитьУдалитьIEnumerable GetMany(Expression> where);
Всё равно сбивается
ОтветитьУдалить