Pull to refresh

Comments 20

UFO just landed and posted this here
В подавляющем большинстве источников все-таки используется именно «репозиторий».
Логично использовать максимально похожий на оригинал вариант, либо заменять термином «хранилище».
это из той же серии, что «браузер» и «броузер»
В cumputer science устоялось именно «репозиторий». Репозитарий же употребляется в контексте ценных бумаг, но и там похоже постепенно идут к написанию через «о».
Исходный английский вариант звучит через «о».
Если же по аналогии, то существует созвучное слово депозитАрий.
А с ним всё просто:
депозитОрий — это место, а депозитАрий — лицо (чаще юридическое).
Поскольку нас интересует место хранения, то пишем через «о» — репозиторий.
Часто приходится использовать похожую организацию в своих проектах. Небольшое отличие: называю объекты не репозиторием (Repository), а хранилищем (Storage).
Хранилище организовано в виде интерфейса, примерно так:

interface StorageInterface {
    public function add($item);
    public function update($item);
    public function delete($item);
    
    public function getById($id);
    public function getByIds(array $id);
}


Далее, следует реализации интерфейса под нужные базы данных, например:

class RedisStorage implements StorageInterface {
    ...
}

class MysqlStorage implements StorageInterface {
    ...
}

class MemoryStorage implements StorageInterface {
    ...
}


Реализации хранилищ баз данных оперируют с массивами/строками и ничего не знают о том, какие данные к ним приходят. Вся валидация и подготовка данных происходит выше.
Следующим слоем абстракции идет реализация хранилищ сущностей в нужных базах данных. Будь то User, News, Event, Point, etc…

class UsersRedisStorage extends RedisStorage {
    public function add(User $item);
    public function update(User $item);
    public function delete(User $item);
}

class PointsMysqlStorage extends MysqlStorage {
    public function add(Point $item);
    public function update(Point $item);
    public function delete(Point $item);
}


На этом слое, производится частичная валидация и подготовка данных. Кейсы вроде:
— База данных принимает фиксированное число полей для сущности, а на вход пришли только обязательные поля? Не беда, добавим недостающие поля со значениями по умолчанию.
— Хранилище базы данных работает с JSON? Сконвертируем и принятые данные и прокинем дальше.

Реализация сущностей проста. Это класс-обертка над данными (можно назвать стрктурой), которая имеет примерно такой интерфейс:

interface Entity {
    public function get($field);
    public function set($field, $value);
    public function imort(array $data);
    
    /**
     * @return array
     */
    public function export();
}


Хранилище сущностей принимает экземпляр сущности, делает экспорт данных и посылает их в реализацию базы данных. Примерно так:

class User implements Entity {
   ...
}

class UsersRedisStorage extends RedisStorage {
    public function add(User $item) {
        $userData = $item->export();
        // Вот тут валидируем $userData
        // Или переводим в json: $userData = json_encode($userData);
        parent::add($userData);
    }

    public function getById($id) {
        $userData = parent::getById($id);
        $User = new User();
        $User->import($userData);
        return $User;
    }
}


Вокруг хранилища сущностей можно обернуть какую-нибудь фабрику или фасад, например.
В итоге, система хранилищ получается очень гибкая. Быстро пишутся реализации хранения различных сущностей в различных базах данных.
Различие между вашими Storage и приведенными автором репозиториями в том, что последние (их интерфейс) принадлежат (или могут принадлежать) доменной области. В то время как Storage это просто внутренние сервисы, не более. Они никакого отношения не имеют к нашим доменным моделям. Скажем имея репозиторий UserRepositoryInterface можно взять вашу реализацию хранилища или взять Doctrine2 с их репозиториями, и для приложения разницы не будет.

namespace Acme\MyApp\Repository;

use Acme\Storage\Storage;
use Acme\MyApp\Domain\Repository\UserRepositoryInterface;

class UserRepository implements UserRepositoryInterface
{
    /**
     * @var Storage
    private $sorage;

    function __constructor(Storage $storage) 
    {
        $this->storage = $storage;
    }

    getBannedUsers()
    {
        // ...
        return $this->storage->find($criteria);
    }

    // ...
}



Ну короче вы поняли, вся соль в интерфейсе а не в названиях и реализации.

Чаще видел что storage используется во фронтенде, а репозитории на беке

UFO just landed and posted this here


1. В PostRepository хранится же author_id или что-то подобное, а значит можно сформировать критерий для фильтрации и вытащить нужные посты

2. Репозиторий — это паттерн для интерфейсов, которые работают с хранением данных, не реализация

Про join интересный вопрос. Получается если по "правильному" использовать репозитории, то нужно делать несколько запросов к бд чтобы получить все необходимые данные?

Захотел откомментить, что интерфейс должен быть на уровне домена, а реализация на уровне приложения, через минуту об этом прочитал. Захотел добавить ссылку на либу пользователя everzet, увидел через минуту. =) Прям согласен с каждым словом в статье!

Добавлю от себя: кто-то (возможно everzet, но я не уверен) предложил очень интересный способ ускорения даже UI тестов. Нужно заменить Doctrine репозитории на репозитории на обычных файлах на время выполнения UI тестов. Т.о. у нас сохраняется persistence, но убирается лишняя нагрузка на парсинг DQL, гидрацию и пр, а так же убирается необходимость чистить БД перед каждым сценарием.
Для интеграционных тестов такой подход норм, но на функциональных/ui тестах подменять сервисы уже как-то не хорошо. Так же стоит при таком подходе не просто файлами вооружиться а иметь какие-то дата-фэктори, аля phpmachinist и т.д. которые умеют сущности конструировать по каким-то правилам. Тогда и поддержка тестов будет не сильно болезненной.
Не понял, при чем тут дата-фэктори? Мы заменяем доктрин репозиторий на репозиторий, который кладет сериализованный массив энтити в файл вместо БД. Базовый класс для такого репозитория можно взять на гитхабе по ссылке в статье, кстати — оно для этого и задумывалось. Про поддержку тестов, смотря с чем сравнивать. Если сравнивать с подходом того же everzt-а Modelling By Example, то использование data-factory на его фоне выглядит так себе.

Хорошо оно ли для функциональных тестов, вопрос, конечно, спорный. Я считаю, что можно написать кучу интеграционных тестов чисто на доктрин-репозитории, а UI тесты уже с моками делать. Сам пока не пробовал такой подход.
Дата фэктори гибче и проще поддерживать, нет обращений к файловой системе (быстрее чем тупо сериализованные энтити которые должны все же откуда-то взяться) и я не вижу в них ничего страшного в контексте Modelling by example.
Мы, видимо, говорим о разных вещах. Вы говорите о том, как готовить данные для тестов. А я говорю о том, как хранить данные внутри тестов. Так вот если весь сценарий проходит внутри одного процесса, то можно юзать InMemoryRepository. Если нет (UI тесты, например, где надо хранить состояние между запросами), то InMemoryRepository уже не подойдет, и надо юзать что-то, что кладет коллекции энтитей в файлы. Например что-то на основе репозитория по ссылке в статье.

Как готовить данные для теста это отдельный вопрос.

Можно даже не файловые делать, а в sqlite, считай можно ту же орм перенастроить с основной бд на скулайт и sql не нужно переписывать

Центральной концепцией ООП является инкапсуляция.
Не согласен, центральная концепция ООП — это полиморфизм.
Sign up to leave a comment.

Articles