Перейти к содержимому

Что такое репозиторий в программировании

  • автор:

Репозитории

Репозитории — это место, где хранятся артефакты: jar файлы, pom файлы, javadoc, исходники. Существуют:

  • Локальный репозиторий
  • Центральный репозиторий
  • Внутренний «Корпоративный» репозиторий

1. Локальный репозиторий

Локальный репозиторий по умолчанию расположен в /.m2/repository — персональный для каждого пользователя. Здесь лежат артефакты, которые были скачаны из центрального репозитория либо добавлены другим способом. Например, если вы наберёте команду

mvn install

в текущем проекте, то соберётся jar (или war, pom в зависимости от содержимого тега packaging), который установится в локальный репозиторий.

2. Центральный репозиторий

Чтобы самому каждый раз не создавать репозиторий, сообщество для Вас поддерживает центральный репозиторий. Если для сборки вашего проекта не хватает зависимостей, то они по умолчанию автоматически скачиваются с http://repo1.maven.org/maven2. В этом репозитории лежат практически все опенсорсные фреймворки и библиотеки.

Самому в центральный репозиторий положить нельзя. Т.к. этот репозиторий используют все, то перед тем как туда попадают артефакты, они проверяются, тем более что если артефакт однажды попал в репозиторий, то по правилам изменить его нельзя.

Для поиска нужной библиотеки очень удобно пользоваться сайтами http://mavenrepository.com/ и http://findjar.com/.

3. Корпоративный репозиторий

Если вы хотите создать свой репозиторий, содержимое которого вы можете полностью контролировать (как локальный), и сделать так, чтобы он был доступен для нескольких человек, вам будет полезен корпоративный репозиторий. Доступ к артефактам можно ограничивать настройками безопасности сервера так, что код ваших проектов не будет доступен извне.

Чтобы добавить репозиторий в список, откуда будут скачиваться зависимости, нужно добавить секцию repositories в pom.xml.

  • Преимущества и недостатки Maven
  • Установка Maven
  • Maven — жизненный цикл сборки
  • Файл pom.xml
  • Плагины Maven
  • Задания

Паттерн ‘Репозиторий’ в 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()

А весь остальной код будет тем же, что и раньше. Таким образом, мы можем уйти от явной привязки к одному хранилищу данных и тем самым повысить гибкость приложения.

Repository (Репозиторий)

Fork me on GitHub

Посредничает между уровнями области определения и распределения данных (domain and data mapping layers), используя интерфейс, схожий с коллекциями для доступа к объектам области определения.

Система со сложной моделью области определения может быть упрощена при помощи дополнительного уровня, например Data Mapper, который бы изолировал объекты от кода доступа к БД. В таких системах может быть полезным добавление ещё одного слоя абстракции поверх слоя распределения данных (Data Mapper), в котором бы был собран код создания запросов. Это становится ещё более важным, когда в области определения множество классов или при сложных, тяжелых запросах. В таких случаях добавление этого уровня особенно помогает сократить дублирование кода запросов.

Паттерн Repository посредничает между слоем области определения и слоем распределения данных, работая, как обычная колекция объектов области определения. Объекты-клиенты создают описание запроса декларативно и направляют их к объекту-репозиторию ( Repository ) для обработки. Объекты могут быть добавлены или удалены из репозитория, как будто они формируют простую коллекцию объектов. А код распределения данных, скрытый в объекте Repository , позаботится о соответсвующих операциях в незаметно для разработчика. В двух словах, паттерн Repository инкапсулирует объекты, представленыые в хранилище данных и операции, производимые над ними, предоставляя более объектно-ориентированное представление реальных данных. Repository также преследует цель достижения полного разделения и односторонней зависимости между уровнями области определения и распределения данных.

Использована иллюстрация с сайта Мартина Фаулера.

  • Главная
  • Список паттернов
  • Сайт создан и поддерживается Василием Кулаковым.

Паттерн «Репозиторий». Основы и разъяснения

Вот как Википедия описывает репозиторий. Так уж случилось, что в отличие от некоторых других жаргонных словечек, с которыми мы имеем дело, этот термин прекрасно передает свою суть. Репозиторий представляет собой концепцию хранения коллекции для сущностей определенного типа.

Репозиторий как коллекция

Вероятно, наиболее важным отличием репозиториев является то, что они представляют собой коллекции объектов. Они не описывают хранение в базах данных или кэширование или решение любой другой технической проблемы. Репозитории представляют коллекции. Как вы храните эти коллекции — это просто деталь реализации.

Я хочу внести ясность в этот вопрос. Репозиторий — это коллекция. Коллекция, которая содержит сущности и может фильтровать и возвращать результат обратно в зависимости от требований вашего приложения. Где и как он хранит эти объекты является ДЕТАЛЬЮ РЕАЛИЗАЦИИ.

В мире 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 о репозиториях на который, безусловно, стоит посмотреть. Внутри вы найдете примеры работы с хранением в памяти и файлах.

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

Если у вас есть вопросы или если ваше мнение отличается от моего, пожалуйста, пишите комментарии ниже.

Как всегда, я намерен обновлять статью, чтобы синхронизировать ее с моим текущим мнением.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *