Мои первые попытки использовать Test Driven Development закончились обычными проблемами с доступом к данным. Почти сразу выяснилось, что:
1. Невозможно использовать настоящую базу данных для запуска тестов. Каждый тест не должен зависеть от результатов других тестов. На практике это означает что каждый тест должен выполняться на чистой базе (в худшем случае), или что каждый тест должен быть обернут в транзакцию, с откаткой после выполнения.
2. Невозможно заменить весь код доступа к данным mock-ами. На самом деле, можно попробовать, но это привязывает тесты к конкретной реализации функционала. Вы вынуждены создавать mock для каждого обращения к DAL. Вы так же вынуждены изменять тесты при рефакторинге, что усложняет разработку сопровождение.
Общепринятое решение этих проблем – использование шаблонов Persistence Ignorance (PI) и Repository. Достаточно погуглить “IQueryable Repository”и вы найдете чуть более 9000 упоминаний чего-то вроде:
interface IRepository
{
IQueryable<T> All<T>();
void Insert<T>(T entity);
void Update<T>(T entity);
void Delete<T>(T entity);
}
…и множество реализаций этого интерфейса поверх LINQ To SQL. Несмотря на разнообразие, практически все они реализуют все один и тот же тип шаблона Repository - “Plain old CLR object” Repository. Это означает, что на базовый шаблон они накладывают довольно сильные ограничения.
Самое серьезное – то, что объекты должны быть “плоскими” в худшем смысле этого слова. Вы не можете заставить POCO Repository загрузить связи между объектами. Вы не можете написать код вроде:
repository.All<Project>()
.Where(p => p.Tasks.Count > 10);
В мире POCO связей вообще не существует! Одну короткую строчку кода вам придется заменить вот этим:
var projectIds = repository.All<Task>()
.GroupBy(t => t.ProjectId)
.Where(gr => gr.Count() > 10)
.Select(gr => gr.Key)
.ToList();
repository.All<Project>()
.Where(p => projectIds.Contains(p.Id));
Даже такой простой пример раздувается до двух запросов, с использованием IN. Написав такой мегакод пару раз, я решил что нужно найти способ запихнуть этот запрос назад в одну строчку. И при этом сохранить поддержку Persistence Ignorance.
Осталось придумать лишь еще одну реализацию шаблона Repository, которая:
1. Полностью поддерживает связи (Associations). Она должна поддерживать выполнение кода вроде:
repository.All<Task>()
.Where(t =>
t.Project.Customer.Name.StartsWith("a"));
2. Поддерживает загрузку связанных объектов (Deep Load). Она должна уметь загружать одиночные и множественные зависимости по требованию.
// задачи, со свойством Project установленным в null
repository.All<Task>();
// задачи с загруженным свойством Project
repository.All<Task>()
.LoadWith(t => t.Project);
// задачи в будущих проектах
// свойство Project не загружено (null)
repository.All<Task>()
.Where(t => t.Project.StartDate > DateTime.Today);
3. Поддерживает перехват запросов (Query Interception), для красивых глобальных фильтров
// задачи видимые текущему пользователю
// поведение по умолчанию
repository.All<Task>();
// все задачи, без учета прав пользователя
repository.All<Task>()
.IgnorePermissions();
// сообщения со статусом != Deleted
// поведение по умолчанию
repository.All<Message>();
// только “удаленные” сообщения
repository.All<Message>()
.Deleted();
// все сообщения
repository.All<Message>()
.IncludeDeleted();
4. Позволяет легко переключиться между LINQ To SQL и In-Memory реализациями, одним ключом в конфиге. Изначально ориентирована на использование IoC/DI.
Мы попытались решить эти проблемы и сделать “старые плоские” объекты не настолько плоскими и старыми. Код и примеры выложены на http://lsda.codeplex.com/. Этот блог будет постепенно расти до полноценной документации
Отзывы, пожелания и патчи приветствуются.
October 21st, 2009 - 11:16 pm
Жаль что вы только начали.
Как раз ищу подобную библиотеку.
October 22nd, 2009 - 9:45 am
2build_your_web: начали - понятие осносительное. мы на нем уже штук 5 проектов сдали.
Попробуй забрать source code с кодеплекса, там есть базовые примеры. Постараюсь до конца недели зарелизить и выложить более полные семплы.
December 2nd, 2009 - 8:36 am
а где можно посомтреть конкретные примеры, где видно преимущества Repository?
February 13th, 2010 - 5:12 am
Для более подробного и внимательного изучения добавил в избранное. Буду изучать
February 27th, 2010 - 6:37 am
и всё эе: неподражаемо.. а82ч
March 4th, 2010 - 1:28 pm
ehh.. good thread ))
March 18th, 2010 - 7:41 pm
Спасибо, очень понравилось
March 19th, 2010 - 3:09 am
Хорошая статья. Действительно было интересно почитать. Не часто такое и встречается та.Наверное стоит подписаться на ваше RSS
March 19th, 2010 - 8:35 pm
Действительно интересно. Побольше бы таких статей.