Что такое шаблон проектирования репозитория?
Проще говоря, это реализация посреднического уровня между приложением и источником данных. Ни одна из сторон не должна быть осведомлена о другой для выполнения своих соответствующих заданий, что позволяет нам иметь несвязанную архитектуру, которая, в свою очередь, помогает масштабировать приложение в высшей лиге без жестких зависимостей.
Почему ты должен заботиться?
Позвольте нам понять это на примере. Представьте, что мы строим интернет-магазин, в котором продаются конфеты со вкусом апельсина. Это небольшой магазин, в котором хранится местный инвентарь, поэтому нам здесь ничего не нужно. Приложение магазина может просто подключиться к базе данных и принимать заказы онлайн в зависимости от того, сколько у вас под рукой товаров. Это будет хорошо работать, так как магазин имеет только один склад снабжения и имеет ограниченную зону действия. Но что произойдет, если этот магазин захочет расширить сферу своей деятельности? Магазин может захотеть расширить свой бизнес в другом городе или по всей стране, и наличие центральной системы инвентаризации было бы очень громоздким.
Теперь, если мы все еще используем модели данных, у нас есть несколько тесно связанных приложений. Приложение витрины магазина должно знать все источники данных, с которыми оно должно взаимодействовать, и это плохой дизайн приложения. Работа приложения витрины здесь состоит в том, чтобы позволить клиентам размещать заказы на конфеты, приложение не должно заботиться об источнике данных, оно не должно отслеживать все различные источники данных. Здесь репозитории данных вступают в игру. В соответствии с шаблоном проектирования репозитория общедоступный API предоставляется через интерфейс, и каждый потребитель (в нашем случае это приложение нашего магазина) использует этот API для связи с источником данных. Какой источник данных используется или как он подключается, это не имеет значения для приложения. Приложение касается только тех данных, которые оно получает, и тех данных, которые оно отправляет для сохранения.
После внедрения шаблона проектирования репозитория репозитории можно создавать для каждого источника данных. Приложению витрины больше не нужно отслеживать какой-либо источник данных, оно просто использует API хранилища для получения необходимых данных.
Это волшебная пуля?
Ну нет это не так. Как и у каждого шаблона дизайна, у него есть свои плюсы и минусы, плюсы и минусы.
Плюсы:
- Разделение интересов; приложению не нужно знать или отслеживать какие-либо или все источники данных.
- Позволяет легко модульное тестирование, так как репозитории привязаны к интерфейсам, которые внедряются в классы во время выполнения.
- СУХОЙ (не повторяйте себя) дизайн, код для запроса и извлечения данных из источника (ов) данных не повторяется.
Минусы:
- Добавляет еще один уровень абстракции, который добавляет определенный уровень сложности, что делает его излишним для небольших приложений.
Как это сделать?
Давайте посмотрим на это с небольшим примером кода. Я буду использовать Laravel здесь в примере, чтобы использовать его превосходную функцию внедрения зависимостей. Если вы используете какой-либо современный PHP-фреймворк, у него уже должен быть контейнер Dependency Injection / IoC. Внедрение зависимостей требуется для реализации шаблона проектирования репозитория, поскольку без него вы не сможете привязать репозиторий данных к интерфейсу репозитория, и вся идея состоит в том, чтобы кодировать интерфейс, чтобы избежать жесткой связи. Если вы не используете какой-либо фреймворк или у вашего фреймворка нет контейнера IoC, вы можете использовать готовый контейнер IoC (см. Сноски).
Давайте взломать дальше. Во-первых, мы настроили наше пространство имен и автозагрузку в Composer. Откройте composer.json
и добавьте автозагрузку psr-4 для нашего пространства имен (в узле autoload
сразу после classmap
).
"autoload" : {
"classmap" : [
"app/commands" ,
"app/controllers" ,
"app/models" ,
"app/database/migrations" ,
"app/database/seeds" ,
"app/tests/TestCase.php"
],
"psr-4" : {
"RocketCandy\\" : "app/RocketCandy"
}
},
После сохранения выполните команду composer dump-autoload -o
в терминале, чтобы зарегистрировать автозагрузку для нового пространства имен. Создайте OrangeCandyRepository.php
в app/RocketCandy/Repositories/OrangeCandyRepository/
. Это будет интерфейс нашего репозитория.
<? php namespace RocketCandy \Repositories\OrangeCandyRepository ;
interface OrangeCandyRepository {
public function get_list ( $limit = 0 , $skip = 0 );
public function get_detail ( $candy_id = 0 );
}
Теперь, когда у нас есть интерфейс, мы можем создать репозиторий. Создайте CityAOrangeCandyRepository.php
в app/RocketCandy/Repositories/OrangeCandyRepository/
.
<? php namespace RocketCandy \Repositories\OrangeCandyRepository ;
class CityAOrangeCandyRepository implements OrangeCandyRepository {
public function get_list ( $limit = 0 , $skip = 0 ) {
//query the data source and get the list of
//candies
}
public function get_detail ( $candy_id = 0 ) {
//query the data source and get the details of
//candies
}
}
Чтобы привязать репозиторий OrangeCandyRepository
интерфейсу OrangeCandyRepository
, мы будем использовать IoC-контейнер Laravel. Откройте app/start/global.php
и добавьте следующее в конец файла.
//OrangeCandyRepository
App :: bind (
'RocketCandy\Repositories\OrangeCandyRepository\OrangeCandyRepository' ,
'RocketCandy\Repositories\OrangeCandyRepository\CityAOrangeCandyRepository'
);
Примечание: я поместил привязку IoC в global.php
только для демонстрации. В идеале они должны быть помещены в отдельный файл, в который вы поместите все привязки IoC и загрузите этот файл здесь, в global.php
или создадите сервис-провайдеров для регистрации каждой привязки IoC. Вы можете прочитать больше здесь .
Теперь мы можем использовать репозиторий через интерфейс. В нашем CandyListingController.php
расположенном в app/controllers/
.
<? php use RocketCandy \Repositories\OrangeCandyRepository\OrangeCandyRepository ;
class CandyListingController extends BaseController {
/** * @var RocketCandy\Repositories\OrangeCandyRepository\OrangeCandyRepository */
protected $_orange_candy ;
public function __construct ( OrangeCandyRepository $orange_candy ) { $this -> _orange_candy = $orange_candy ;
}
}
Здесь мы OrangeCandyRepository
интерфейс OrangeCandyRepository
в наш контроллер и сохраняем ссылку на его объект в переменной класса, которая теперь может использоваться любой функцией в контроллере для запроса данных. Поскольку мы связали интерфейс CityAOrangeCandyRepository
репозиторием CityAOrangeCandyRepository
, это будет так, как если бы мы непосредственно использовали CityAOrangeCandyRepository
репозиторий CityAOrangeCandyRepository
.
Так что теперь тип и тип источника данных — единственная забота CityAOrangeCandyRepository
здесь. Наше приложение знает только интерфейс OrangeCandyRepository
и API, который оно предоставляет, которому должен соответствовать каждый реализующий его репозиторий. Репозиторий разрешается из контейнера IoC во время выполнения, что означает, что привязка интерфейса <=> к репозиторию может быть установлена по мере необходимости, интерфейс может быть привязан к любому репозиторию данных, и наше приложение не должно беспокоиться об изменении данных источником, который теперь может быть базой данных, веб-службой или транс-мерным каналом гиперданных.
Один размер не подходит для всех
Как я уже упоминал выше в «Минусах шаблона проектирования репозитория», он немного усложняет приложение. Поэтому, если вы создаете небольшое приложение и не видите его перехода в высшую лигу, где может быть задействовано более одного источника данных, вам лучше не реализовывать это и придерживаться старых добрых моделей данных. Знание чего-либо отличается от знания того, когда его использовать. Это очень удобный шаблон проектирования, который экономит много головной боли как при создании приложения, так и когда его необходимо поддерживать или масштабировать (или уменьшать), но это не волшебная палочка для каждого приложения.
Я использовал специальный код Laravel для демонстрации реализации выше, но он довольно прост и похож на любой приличный контейнер IoC. Есть вопросы? Огонь в комментариях ниже.
Примечания:
- Вот некоторые библиотеки контейнеров IoC, которые вы можете использовать, если в вашей инфраструктуре их нет или вы не используете платформу:
- Предлагаемое чтение: