Паттерн «Репозиторий». Основы и разъяснения
Вот как Википедия описывает репозиторий. Так уж случилось, что в отличие от некоторых других жаргонных словечек, с которыми мы имеем дело, этот термин прекрасно передает свою суть. Репозиторий представляет собой концепцию хранения коллекции для сущностей определенного типа.
Репозиторий как коллекция
Вероятно, наиболее важным отличием репозиториев является то, что они представляют собой коллекции объектов. Они не описывают хранение в базах данных или кэширование или решение любой другой технической проблемы. Репозитории представляют коллекции. Как вы храните эти коллекции — это просто деталь реализации.
Я хочу внести ясность в этот вопрос. Репозиторий — это коллекция. Коллекция, которая содержит сущности и может фильтровать и возвращать результат обратно в зависимости от требований вашего приложения. Где и как он хранит эти объекты является ДЕТАЛЬЮ РЕАЛИЗАЦИИ.
В мире PHP мы привыкли к циклу запрос/ответ, который оканчивается смертью процесса. Все, что пришло извне и не сохранилось — ушло навсегда, в этой точке. Так вот, не все платформы работают именно так.
Хорошим способом понять как работают репозитории является представление вашего приложения постоянно работающим, в этом случае все объекты остаются в памяти. Вероятность критических сбоев и реакцию на них в этом эксперименте можно пренебречь. Представьте, что у вас есть Singleton-экземпляр репозитория для сущностей Member , MemberRepository .
Затем создайте новый объект Member и добавьте его в репозиторий. Позже, вы запросите у репозитория все элементы, хранящиеся в нем, таким образом вы получите коллекцию, которая содержит этот объект внутри. Возможно вы захотите получить какой-то конкретный объект по его ID, это также возможно. Очень легко представить себе, что внутри репозитория эти объекты хранятся в массиве или, что еще лучше, в объекте-коллекции.
Проще говоря, репозиторий — это особый вид надежных коллекций, которые вы будете использовать снова и снова, чтобы хранить и фильтровать сущности.
Взаимодействие с Репозиторием
Представьте, что мы создаем сущность Member . Мы приводим объект к необходимому состоянию, затем запрос заканчивается и объект исчезает. Пользователь пытается авторизоваться в нашем приложении и не может. Очевидно, что нам необходимо сделать этот объект доступным и для других частей приложения.
$member = Member::register($email, $password); $memberRepository->save($member);
Теперь мы можем получить доступ к объекту позже. Примерно так:
$member = $memberRepository->findByEmail($email); // or $members = $memberRepository->getAll();
Мы можем хранить объекты в одной части нашего приложения, а затем извлекать их из другой.
Должны ли репозитории создавать сущности?
Вы можете встретить такие примеры:
$member = $memberRepository->create($email, $password);
Я видел множество аргументов приводящихся в пользу этого, но совершенно не заинтересован в подобном подходе.
Прежде всего, репозитории — это коллекции. Я не уверен в том, зачем коллекция должна быть коллекцией и фабрикой. Я слышал аргументы вроде «если обращаться удобнее так, то почему бы не повесить обработчик на подобные действия»?
На мой взгляд, это анти-паттерн. Почему бы не позволить классу Member , иметь свое собственное понимание как и зачем создается объект или почему бы не сделать отдельную фабрику для создания более сложных объектов?
Если мы относимся к нашим репозиториям как к простым коллекциям, так значит и не нужно нагружать их лишним функционалом. Я не хочу классов коллекций, которые ведут себя как фабрики.
В чем выгода использования репозиториев?
Основное преимущество репозиториев — это абстрактный механизм хранения для коллекций сущностей.
Предоставляя интерфейс MemberRepository мы развязываем руки разработчику, который уже сам решит как и где хранить данные.
interface MemberRepository
class ArrayMemberRepository implements MemberRepository < private $members = []; public function save(Member $member) < $this->members[(string)$member->getId()] = $member; > public function getAll() < return $this->members; > public function findById(MemberId $memberId) < if (isset($this->members[(string)$memberId])) < return $this->members[(string)$memberId]; > > >
class RedisMemberRepository implements MemberRepository < public function save(Member $member) < // . >// you get the point >
Таким образом, большинство наших приложений знает только абстрактное понятие MemberRepository и его использование может быть отделено от фактической реализации. Это очень раскрепощает.
К чему относятся репозитории: Domain или Application Service Layer?
Итак, вот интересный вопрос. Во-первых, давайте определим, что Application Service Layer — это многоуровневая архитектура, которая отвечает за специфические детали реализации приложения, такие как целостность базы данных, и различные реализации работы с интернет-протоколами (отправка электронной почты, API) и др.
Определим термин Domain Layer как слой многоуровневой архитектуры, которая отвечает за бизнес-правила и бизнес-логику.
Куда же попадет репозиторий при таком подходе?
Давайте посмотрим на нашем примере. Вот код, написанный ранее.
class ArrayMemberRepository implements MemberRepository < private $members = []; public function save(Member $member) < $this->members[(string) $member->getId()] = $member; > public function getAll() < return $this->members; > public function findById(MemberId $memberId) < if (isset($this->members[(string)$memberId])) < return $this->members[(string)$memberId]; > > >
В этом примере я вижу много деталей реализации. Они, несомненно, должны входить в слой приложения
А теперь давайте удалим все детали реализации из этого класса…
class ArrayMemberRepository implements MemberRepository < public function save(Member $member) < >public function getAll() < >public function findById(MemberId $memberId) < >>
Хм… это начинает выглядеть знакомо… Что же мы забыли?
Возможно, получившийся код напоминает вам это?
interface MemberRepository
Это означает, что интерфейс находится на границе слоев. и на самом деле может содержать доменно-специфические концепты, но сама реализация не должна этого делать.
Интерфейсы репозиториев принадлежат к слою домена. Реализация же относятся к слою приложения. Это означает, что мы свободны при построении архитектуры на уровне доменного слоя без необходимости зависеть от слоя сервиса.
Свобода смены хранилищ данных
Всякий раз, когда вы слышите чей-то разговор о концепции объектно-ориентированного дизайна, вы, наверное, могли слышать что-то вроде «… и у вас есть возможность поменять одну реализацию хранения данных на другую в будущем. «
По-моему, это не совсем правда… я бы даже сказал, что это очень плохой аргумент. Самой большой проблемой объяснения концепции репозиториев является то, что сразу напрашивается вопрос «вы действительно хотите это делать?». Я НЕ хочу чтобы подобные вопросы влияли на использование паттерна репозитория.
Любое достаточно хорошо спроектированное объектно-ориентированное приложение автоматически подходит под приведенное преемущество. Центральной концепцией ООП является инкапсуляция. Вы можете предоставить доступ к API и скрыть реализацию.
Ведь вы же на самом деле не будете переключаться с одного ORM на другой и обратно. Но даже если вы захотите так делать, то, по крайней мере, у вас будет возможность сделать это. Однако, замена реализации репозитория будет огромным плюсом при тестировании.
Тестирование при использовании паттерна «Репозиторий»
Ну, тут все просто. Давайте предположим, что у вас есть объект, который обрабатывает что-то вроде регистрации участников…
class RegisterMemberHandler < private $members; public function __construct(MemberRepository $members) < $this->members = $members; > public function handle(RegisterMember $command) < $member = Member::register($command->email, $command->password); $this->members->save($member); > >
Во время очередной операции, я могу взять экземпляр DoctrineMemberRepository . Однако, во время тестирования легко можно заменить его на экземпляр ArrayMemberRepository. Они оба реализуют один и тот же интерфейс.
Упрощенный пример теста может выглядеть примерно так…
$repo = new ArrayMemberRepository; $handler = new RegisterMemberHandler($repo); $request = $this->createRequest(['email' => 'bob@bob.com', 'password' => 'angelofdestruction']); $handler->handle(RegisterMember::usingForm($request)); AssertCount(1, $repo->findByEmail('bob@bob.com'));
В этом примере мы тестируем обработчик. Нам не нужно проверять корректность хранения данных репозитория в БД (или еще где). Мы тестируем конкретное поведение этого объекта: регистрируем пользователя на основе данных формы, а затем передаем их в репозиторий.
Коллекция или Состояние
В книге Implementing Domain-Driven Design Vaughn Vernon делает различие между типами репозиториев. Идея коллекцио-ориентированного репозитория (ориг. — collection-oriented repository) в том, что работа с репозиторием идет в памяти, как с массивом. Репозиторий, ориентированный на хранение состояний (ориг. — persistence-oriented repository) содержит в себе идею, что в нем будет какая-то более глубокая и продуманная система хранения. По сути различия лишь в названиях.
// collection-oriented $memberRepository->add($member); // vs persistence-oriented $memberRepository->save($member);
Замечу, что это лишь мое мнение и пока что я придерживаюсь именно его в вопросах использования репозиториев. Однако, хотел бы предупредить, что возможно могу передумать. В конце-концов, я сосредотачиваюсь на них как на коллекциях объектов с теми же обязанностями, что и у любого другого объекта-коллекции.
Дополнительная информация
everzet создал проект на Github о репозиториях на который, безусловно, стоит посмотреть. Внутри вы найдете примеры работы с хранением в памяти и файлах.
Итоги
- … важно дать репозиториям сингулярную задачу функционировать как коллекция объектов.
- … мы не должны использовать репозитории для создания новых экземпляров объектов.
- … мы должны избегать использования репозиториев как способа перехода от одной технологии к другой, так как они имеют очень много преимуществ, от которых трудно отказаться.
Если у вас есть вопросы или если ваше мнение отличается от моего, пожалуйста, пишите комментарии ниже.
Как всегда, я намерен обновлять статью, чтобы синхронизировать ее с моим текущим мнением.
Репозиторий — что это такое? Определение, значение, перевод

Репозиторий (ударение на вторую «о») это электронное хранилище данных, причём как правило речь идёт о программном коде. У репозиториев две основные функции, каждую из которых мы сейчас разжуём помягче.
Во-первых, репозитории служат для распространения полезных программ. За долгие десятилетия существования компьютеров человеки написали миллионы строчек полезного кода, которым можно воспользоваться, чтобы не писать его самому. В репозиториях хранятся стабильно работающие и проверенные версии программ, которые можно либо скачать бесплатно, без регистрации и СМС, либо таки заплатить, хотя такое встречается реже. Можно сравнить публичные репозитории с такими же библиотеками, куда каждый желающий может зайти и взять себе крупицу знаний.
Во-вторых, программисты используют репозитории для контроля версий, который особенно важен при разработке одной и той же программы несколькими программистами. В репозиториях хранятся стабильные версии программы, причём каждое изменение должно быть задокументировано: кто, когда и зачем его сделал. Такой подход к программированию позволяет выявлять накосячивших программистов и откатывать программы к более ранней версии.
Метки / Компьютеры
Понравилась страница?
Пожалуйста, поделитесь ссылкой с друзьями:
Repository (Репозиторий)

Посредничает между уровнями области определения и распределения данных (domain and data mapping layers), используя интерфейс, схожий с коллекциями для доступа к объектам области определения.
Система со сложной моделью области определения может быть упрощена при помощи дополнительного уровня, например Data Mapper, который бы изолировал объекты от кода доступа к БД. В таких системах может быть полезным добавление ещё одного слоя абстракции поверх слоя распределения данных (Data Mapper), в котором бы был собран код создания запросов. Это становится ещё более важным, когда в области определения множество классов или при сложных, тяжелых запросах. В таких случаях добавление этого уровня особенно помогает сократить дублирование кода запросов.
Паттерн Repository посредничает между слоем области определения и слоем распределения данных, работая, как обычная колекция объектов области определения. Объекты-клиенты создают описание запроса декларативно и направляют их к объекту-репозиторию ( Repository ) для обработки. Объекты могут быть добавлены или удалены из репозитория, как будто они формируют простую коллекцию объектов. А код распределения данных, скрытый в объекте Repository , позаботится о соответсвующих операциях в незаметно для разработчика. В двух словах, паттерн Repository инкапсулирует объекты, представленыые в хранилище данных и операции, производимые над ними, предоставляя более объектно-ориентированное представление реальных данных. Repository также преследует цель достижения полного разделения и односторонней зависимости между уровнями области определения и распределения данных.
Использована иллюстрация с сайта Мартина Фаулера.
- Главная
- Список паттернов
- Сайт создан и поддерживается Василием Кулаковым.
Паттерн ‘Репозиторий’ в ASP.NET
Одним из наиболее часто используемых паттернов при работе с данными является паттерн ‘Репозиторий’. Репозиторий позволяет абстрагироваться от конкретных подключений к источникам данных, с которыми работает программа, и является промежуточным звеном между классами, непосредственно взаимодействующими с данными, и остальной программой.
Допустим, у нас есть одно подключение к базе данных MS SQL Server. Однако, что если в какой-то момент времени мы захотим сменить подключение с MS SQL на другое — например, к бд MySQL или MongoDB. При стандартном подходе даже в небольшом приложении, осуществляющем выборку, добавление, изменение и удаление данных, нам бы пришлось сделать большое количество изменений. Либо в процессе работы программы в зависимости от разных условий мы хотим использовать два разных подключения. Таким образом, репозиторий добавляет программе гибкость при работе с разными типами подключений.
Например, у нас в программе есть следующий класс модели, с которой мы работаем:
public class Book < public int Id < get; set; >public string Name < get; set; >public string Author < get; set; >public int Price < get; set; >>
И также имеется следующий класс контекста данных:
public class BookContext : DbContext < public BookContext() : base("DefaultConnection") < >public DbSet Books < get; set; >>
Пусть в файле web.config у меня определено два подключения:
Первое подключение используется контекстом данных для работы с бд MS SQL Server. Второе подключение — подключение к БД MongoDB.
Теперь определим интерфейс репозитория:
interface IRepository : IDisposable where T : class < IEnumerableGetBookList(); // получение всех объектов T GetBook(int id); // получение одного объекта по id void Create(T item); // создание объекта void Update(T item); // обновление объекта void Delete(int id); // удаление объекта по id void Save(); // сохранение изменений >
В данном случае мы создали обобщенный интерфейс, так как он более гибкий, по сравнению с обычной. Хотя так как у нас работа идет только с классом Book, можно было бы сделать и необобщенный вариант:
interface IRepository : IDisposable < IEnumerableGetBookList(); Book GetBook(int id); void Create(Book item); void Update(Book item); void Delete(int id); void Save(); >
На случай, если контекст данных будет подразумевать освобождение или закрытие подключений, интерфейс репозитория применяет интерфейс IDisposable.
Теперь создадим непосредственную реализацию для работы с MS SQL Server:
public class SQLBookRepository : IRepository < private BookContext db; public SQLBookRepository() < this.db = new BookContext(); >public IEnumerable GetBookList() < return db.Books; >public Book GetBook(int id) < return db.Books.Find(id); >public void Create(Book book) < db.Books.Add(book); >public void Update(Book book) < db.Entry(book).State = EntityState.Modified; >public void Delete(int id) < Book book = db.Books.Find(id); if(book!=null) db.Books.Remove(book); >public void Save() < db.SaveChanges(); >private bool disposed = false; public virtual void Dispose(bool disposing) < if(!this.disposed) < if(disposing) < db.Dispose(); >> this.disposed = true; > public void Dispose() < Dispose(true); GC.SuppressFinalize(this); >>
Почти все методы SQLBookRepository работают с контекстом данных, который создается в конструкторе класса.
Теперь применим репозиторий в контроллере:
public class HomeController : Controller < IRepositorydb; public HomeController() < db = new SQLBookRepository(); >public ActionResult Index() < return View(db.GetBookList()); >public ActionResult Create() < return View(); >[HttpPost] public ActionResult Create(Book book) < if(ModelState.IsValid) < db.Create(book); db.Save(); return RedirectToAction("Index"); >return View(book); > public ActionResult Edit(int id) < Book book = db.GetBook(id); return View(book); >[HttpPost] public ActionResult Edit(Book book) < if (ModelState.IsValid) < db.Update(book); db.Save(); return RedirectToAction("Index"); >return View(book); > [HttpGet] public ActionResult Delete(int id) < Book b = db.GetBook(id); return View(b); >[HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) < db.Delete(id); return RedirectToAction("Index"); >protected override void Dispose(bool disposing) < db.Dispose(); base.Dispose(disposing); >>
В данном случае в всех методах контроллера мы работаем не с методами конкретного класса, а с методами интерфейса IRepository. И только в конструкторе контроллера мы определяем непосредственный тип репозитория: db = new SQLBookRepository(); . Таким образом, мы практически избавляемся от зависимости к определенному типу подключения.
Но у меня в файле web.config определено еще подключение к MongoDB. И теперь создадим репозиторий, который будет использовать это подключение:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.Builders; using System.Configuration; public class MongoBookRepository : IRepository < MongoClient client; MongoServer server; MongoDatabase database; public MongoBookRepository() < var con = new MongoConnectionStringBuilder( ConfigurationManager.ConnectionStrings["MongoDb"].ConnectionString); client = new MongoClient(con.ConnectionString); server = client.GetServer(); database = server.GetDatabase(con.DatabaseName); >public MongoCollection Books < get < return database.GetCollection("books"); > > public IEnumerable GetBookList() < return Books.FindAll(); >public Book GetBook(int id) < return Books.FindOneById(id); >public void Create(Book book) < try < int if (Books.Count() >0) => x.Id); book.Id = ++id; Books.Insert(book); > catch < >> public void Update(Book book) < try < Books.Save(book); >catch < >> public void Delete(int id) < try < var query = Query.EQ(e => e.Id, id); if (query != null) < Books.Remove(query); >> catch < >> public void Save() <> public void Dispose() <> >
Для работы с MongoDB используется другой API, однако, так как класс MongoBookRepository применяет тот же интерфейс репозитория, то общий набор методов у этого класса будет то же, что и у SqlBookRepository. Поэтому при необходимости можно просто заменить в контроллере тип репозитария:
public HomeController()
А весь остальной код будет тем же, что и раньше. Таким образом, мы можем уйти от явной привязки к одному хранилищу данных и тем самым повысить гибкость приложения.