
Прошло почти три года с тех пор как я впервые написал о своём отказе от такой абстракции как репозиторий (Repository). С тех пор я практически не использовал никаких концепций репозитория в системах, которые мы разрабатываем. Я не убирал из проектов уже существующие репозитории, но теперь я просто не нахожу в них никакой ценности в качестве абстракций.
Репозитории, которые создают разработчики, в основном бывают двух видов:
- Абстракции вокруг ORM-фреймворка,
- Инкапсуляция запросов.
Примером первого случая может быть что-нибудь вроде этого:
public interface IConferenceRepository { IRavenQueryable<Conference> Query(); Conference Load(Guid id); }
Инкапсуляция запросов обычно занимает несколько больше строк:
public interface IConferenceRepository { IEnumerable<Conference> FindAll(); IEnumerable<Conference> FindFuture(); IEnumerable<Conference> FindFree(); IEnumerable<Conference> FindPaid(); }
Здесь каждый метод инкапсулирует один запрос. Оба случая представляют ценность в определённых сценариях. Если у меня цель — абстрагироваться от моей ORM, я пойду первым путём, и, возможно, включу второй тоже.
Но является ли ORM тем, что требует абстракции? Я так не думаю – абстрагирование от чего-то подобного ORM активно мешает мне использовать его мощный функционал. ORM уже является абстракцией, мы действительно должны спросить сами себя, нужна ли нам абстракция абстракции?
Копаем глубже
В первую очередь мы должны вернуться к вопросу, для чего мы стали использовать шаблон репозиторий? Наверняка это было сделано во имя «тестируемости». Тогда давайте начнём с чего-то подобного:
public ActionResult Index() { RavenQueryStatistics stats; var posts = RavenSession.Query<Post>() .Include(x => x.AuthorId) .Statistics(out stats) .WhereIsPublicPost() .OrderByDescending(post => post.PublishAt) .Paging(CurrentPage, DefaultPage, PageSize) .ToList(); return ListView(stats.TotalResults, posts); }
Кажется сложным? Нет. Хотя если сложность будет расти, мы всё ещё будем ограничивать её масштаб одним этим методом. Если мы выведем этот запрос в отдельный класс, репозиторий или метод расширения (extension method), сам запрос всё равно останется в одном методе. С точки зрения метода контроллера, имеет ли значение, где этот код находится – в контроллере или другом классе?
Как насчёт более сложного примера:
public ActionResult Archive(int year, int? month, int? day) { RavenQueryStatistics stats; var postsQuery = RavenSession.Query<Post>() .Include(x => x.AuthorId) .Statistics(out stats) .WhereIsPublicPost() .Where(post => post.PublishAt.Year == year); if (month != null) postsQuery = postsQuery.Where(post => post.PublishAt.Month == month.Value); if (day != null) postsQuery = postsQuery.Where(post => post.PublishAt.Day == day.Value); var posts = postsQuery.OrderByDescending(post => post.PublishAt) .Paging(CurrentPage, DefaultPage, PageSize) .ToList(); return ListView(stats.TotalResults, posts); }
Опять, это просто набор запросов. Я всё ещё хочу инкапсулировать это в одном месте, но я не вижу причин перемещать этот код оттуда, где он уже сейчас. Если запрос поменяется, я просто поменяю код в одном месте. Дополнительная абстракция в этом случае может только сбить с толку.
Нюанс возникает в случае, если у меня несколько концепций, с которыми я работаю в методе контроллера. Давайте посмотрим на метод контроллера, который должен делать несколько вещей:
[ValidateInput(false)] [HttpPost] public ActionResult Comment(CommentInput input, int id, Guid key) { var post = RavenSession .Include<Post>(x => x.CommentsId) .Load(id); if (post == null || post.IsPublicPost(key) == false) return HttpNotFound(); var comments = RavenSession.Load<PostComments>(post.CommentsId); if (comments == null) return HttpNotFound(); var commenter = RavenSession.GetCommenter(input.CommenterKey); if (commenter == null) { input.CommenterKey = Guid.NewGuid(); } ValidateCommentsAllowed(post, comments); ValidateCaptcha(input, commenter); if (ModelState.IsValid == false) return PostingCommentFailed(post, input, key); TaskExecutor.ExcuteLater(new AddCommentTask(input, Request.MapTo<AddCommentTask.RequestValues>(), id)); CommenterUtil.SetCommenterCookie(Response, input.CommenterKey.MapTo<string>()); return PostingCommentSucceeded(post, input); }
В этом случае присутствует много валидации, но настоящая работа отдана объекту AddCommentTask
. Это объект-команда, которая позаботится о выполнении задачи вне MVC, валидаций, ActionResult и тому подобное.
Мы сделали из наших абстракций некоторые концепции (задачи, как AddCommentTask
) и в случае чего мы можем сделать тоже самое с запросами.
Стратегии тестирования
Моя стратегия тестирования на сегодняшний день это:
- Юнит-тестирование изолированных компонентов (доменные модели и другие уже изолированные классы)
- Интеграционное тестирование всего остального
Я не использую контейнеров для авто-мокинга. Я выношу в стабы компоненты, которые я не могу проконтролировать. В противном случае это превращается в стратегию по запихиванию логики всё глубже и глубже.
Для чего-то вроде баз данных мои тесты будут медленнее. И я предпочитаю принять это, потому что это даёт мне лёгкость при рефакторинге. Мои тесты не ломаются только потому, что какой-то стаб надо переделать.
В своих контроллерах я просто предпочту иметь интерфейс (seam, шов — прим. ред.) для тестирования. В проекте RaccoonBlog это означает, что простой заменой механизма хранения RavenDB на in-memory сделает мои тесты намного быстрее.
Но даже в противном случае – я не беспокоюсь добавлении репозитория. По моему опыту, введение репозитория только для того, чтобы вынести что-то наружу – потеря времени. Это добавляет ненужную абстракцию в том месте, где было бы достаточно какой-то концепции (например, инкапсулирования объекта запроса).
Вместо сосредоточения усилий на абстракциях, я фокусируюсь на концепциях, и позволяю тестам падать там, где они могут. В конце концов мои контроллеры не являются объектно-ориентированными – они процедурные (как это подтверждают выдвигаемые к ним требования).
Jimmy Bogard – архитектор в компании Headsrping, создатель AutoMapper и соавтор книги ASP.NET MVC in Action. В своём блоге он фокусируется на DDD, CQRS, распределенных системах и сопряжённых архитектурах и методологиях.
ссылка на оригинал статьи http://habrahabr.ru/post/161703/
Добавить комментарий