Skip to content

Instantly share code, notes, and snippets.

@maestrow
Last active May 1, 2024 06:26
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save maestrow/594fd9aee859c809b043 to your computer and use it in GitHub Desktop.
Save maestrow/594fd9aee859c809b043 to your computer and use it in GitHub Desktop.
Паттерн репозиторий

Паттерн репозиторий

Репозиторий - это слой абстракции, инкапсулирующий в себе всё, что относится к способу хранения данных. Назначение: Разделение бизнес-логики от деталей реализации слоя доступа к данным.

Паттерн Репозиторий стал популярным благодаря DDD (Domain Driven Design). В противоположность к Database Driven Design в DDD разработка начинается с проектирования бизнес логики, принимая во внимание только особенности предметной области и игнорируя все, что связано с особенностями базы данных или других способов хранения данных. Способ хранения бизнес объектов реализуется во вторую очередь.

Применение данного паттерна не предполагает создание только одного объекта репозитория во всем приложении. Хорошей практикой считается создание отдельных репозиториев для каждого бизнес-объекта или контекста, например: OrdersRepository, UsersRepository, AdminRepository.

Пример

public interface IPostsRepository
{
    void Save(Post mypost);
    Post Get(int id);
    PaginatedResult<Post> List(int skip,int pageSize);
    PaginatedResult<Post> SearchByTitle(string title,int skip,int pageSize);
}
  • Метод save определяет какой объект передан - новый или существующий, в зависимости от этого использует команду insert или update.
  • Методы List и SearchByTitle возвращают список постов, в котором указывается информация об отобранной странице.

Generic Repository это антипаттерн

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

public interface IRepository<T>
{
   IEnumerable<T> GetAll();
   T GetByID(int id);   
   void Add(T entity);
   void Update(T entity);
   void Delete(T entity);
}

Репозиторий и ORM

Возможно у вас возник вопрос: Зачем использовать репозиторий, если я использую ORM?

Действительно, ORM позволяет:

  • работать с данными, оперируя бизнес-объектами (POCO).
  • легко сменить поставщика данных (SQL Server на MySql и т.д.)

Однако может быть масса случаев, когда хранение данных представляет собой нечто более сложное или специфичное, чем просто ORM. И тогда такой слой данных инкапсулируется с помощью паттерна репозиторий:

  • Если вы хотите сменить поставщика данных на NoSql базу данных или файлы.
  • Вы можете использовать несколько поставщиков данных. Например, MySql для данных и файловую систему для хранения изображений.
  • Строго говоря модель ORM не является моделью предметной области. И в случае когда это действительно разные вещи, а используется только модель ORM, то это уже не DDD.

Если ваш репозиторий возвращает IQueryable, то это ошибка, т.к. репозиторий не должен возвращать что-либо относящееся к способу хранения. Правильно было бы IEnumerable.

Преимущества паттерна Репозиторий

  • Выделение общей логики (DRY): проверки, значения по умолчанию, логирование и т.д..
  • Независимость бизнес-логики от способа хранения. Используя репозиторий, вы обращаетесь с коллекциями бизнес объектов (POCO), но не с database related объектами, не с Data Access Objects. Возможность использовать разные способы хранения: ORM, rdbms, cloud storage, file system etc, заменять их и комбинировать.
  • Работая через интерфейсы вы можете создать несколько реализаций репозитория. Это помогает при тестировании (можно передавать заглушку репозитория при тестировании бизнес-логики) и при изменении способа хранения данных. Конкретная реализация репозитория может регистрироваться в IoC и т.о. ни один модуль приложения за исключением точки входа не будет связан с модулем реализации репозитория. Приложение будет работаеть с интерфейсом репозитория и объектами бизнес-логики.

Репозиторий и DAL (Data Access Layer, Persistence Layer)

Репозиторий - это высокоуровневая абстракция доступа к данным. Интерфейс каждого конкретного репозитория (контракт) определяется в слое бизнес-логики наряду с классами предметной области. Реализация каждого репозитория находится в слое доступа к данным DAL. Соответственно DAL состоит из реализации каждого репозитория, ORM-специфичных классов, сущностей, классов-сопоставлений (mapping), контекстов данных и т.д.

Ссылки

Источник: Серия статей by Mike Mogosanu из его блога https://blog.sapiensworks.com:

На момент написания этой заметки статьи Mike размещались в категории Repository, затем он переделал свой блог и эти статьи попали в категорию Best Practces, что охватывает и другие вопросы, выходящие за рамки темы Паттерн Репозиторий. Поэтому привожу здесь ссылку на версию статей 2014 года.

@dimuska139
Copy link

@Masynchin так речь идёт про тестирование бизнес-логики в unit-тестах. Заглушки репозиториев там нужны, чтобы не было реальных запросов к БД. Т.е. происходит изоляция бизнес-логики в тесте - чтобы тестировать именно её, а не нижележащий слой приложения (репозитории). Если нужно реальные запросы к БД тестировать, то это уже другой вид тестов.

@Yoyo19911
Copy link

Добрый день, есть вопрос:
Если интерфейс репозитория каждой бизнес-сущности свой, то как реализовать тот самый DRY?
Куда и как выносить общую логику?

Приходит в голову только вариант с ругаемым наследованием и кривоватый вариант с композицией. В обоих случаях мы намертво привязаны к реализации и при изменении хранения данных, нам придется в каждом репозитории руками что-то править.
Есть конечно вариант ссылаться на интерфейс при композиции, но я не уверен, что возможно без сурового оверхэда создать достаточно универсальный интерфейс для всех возможных случаев хранения.

@dimuska139
Copy link

@Yoyo19911 вы имеете в виду, куда выносить из репозиториев различные похожие методы вроде GetByID, чтобы их не дублировать? Если да, то я бы тут не советовал сильно следовать DRY. Во-первых, эти методы нужны не в каждом репозитории, а только там, где это реально нужно. И в интерфейсе репозитория в слое бизнес-логики должны быть объявлены только те методы репозитория, которые в этом сервисе используются, а не все существующие в репозитории. Не нужно следовать принципу "а вдруг пригодится?". Во-вторых, эти методы все же не однотипны, ведь они возвращают объекты разных типов с разными свойствами.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment