Взаимодействие нескольких репозиториев. Как объединить?

Здравствуйте.

У меня в системе есть примерно вот такая структура:

shop/
--entity/
------Product.php
------ProductType.php
------Category.php
--repository/
------ProductRepository.php
------CategoryRepository.php
------ProductTypeRepository.php
.... и еще....


interface CategoryRepositoryInterface
{
     public function findOne($id): CategoryInterface;
     public function findAll(): array;
     public function save(CategoryInterface $category);
     public function remove(CategoryInterface $category);
}

interface ProductRepository
{
   // .... все тоже самое, что и у CategoryRepositoryInterface
}


Product состоит из двух дополнительных частей (сильно упрощено, сущность с товаром довольно толстая):
interface Product
{
     public function addCategory(CategoryInterface $category);

     public function setProductType(ProductTypeInterface $productTypeInterface);
}


И тут возникает несколько проблем.
Первое, в ProductRepository нужно собрать из всех других репозиториев и вернуть через findOne() товар со всеми зависимостями (тип товара, категория, изображения, лицензии и прочее). Получается, что ProductRepository зависит от других репозиториев (Category, ProductType, Image, File....).

При удалении категории через $categoryRepository->remove() удаляются и товары (там бизнес логика на удаление завязана). Получается ад, репозиторий с категориями зависит от товара. А репозиторий с товарами зависит от репозитория с категориями и еще от нескольких других репозиториев. И тут у меня наступил dependency-hell.

В клиентском коде все должно быть просто:
$productRepository->findOne(100); // вжух и мы получили товар со всеми зависимостями и ИД 100.


Теперь главный вопрос - как собирать товар (или другую сущность) из разных кусков?

Сейчас я вызываю в методе findOne() репозитория соответствующие методы репозиториев других частей. Чувствую, что это неправильно, но как быть? Какие у вас практики, когда сущность собирается из разных кусков?

Как обрабатывать метод $productRepository->save($product), если разные куски товара сохраняются по-разному. Я сейчас прямо из метода save() дергаю отдельные репозитории в транзакции и сохраняю их. Правильно ли это?
Как быть с масштабированием, если в будущем появится еще что-то?
  • Вопрос задан
  • 705 просмотров
Решения вопроса 1
SamDark
@SamDark
Yii2 core team
Получается, что ProductRepository зависит от других репозиториев (Category, ProductType, Image, File....).


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

Теперь главный вопрос - как собирать товар (или другую сущность) из разных кусков?


Руками.

Как обрабатывать метод $productRepository->save($product), если разные куски товара сохраняются по-разному.


Индивидуально. Руками.

Как быть с масштабированием, если в будущем появится еще что-то?


Или взять ORM или, опять же, руками. Не бойтесь писать код и не бойтесь его дублирования в разумных пределах. Не всё то DRY что им кажется.
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
@nicandr
Максим Федоров правильно вам написал, вам нужен еще один слой который будет взаимодействовать с вашими репозиториями, в симфони как и в лбдом другом фреимворке вся логика должна быть в сервисе. Из вашего описания вы сделали facade паттерн чтобы в клиентском коде все бьло просто. Для этого подойдет сервис, который может который так же модет наследовать(implement) интерфейс.

interface CategoryRepositoryInterface
{
     public function findOne($id);
     public function findAll(): array;
     public function save(CategoryInterface $category);
     public function remove(CategoryInterface $category);
}
class CustomNameOfClass implements CategoryRepositoryInterface // ну тут имя лучше изменить
{
      public function findOne($id){};  // тут тоже лучше иметь return type
     public function findAll(){}: array;
     public function save(CategoryInterface $category){}; // тут тоже лучше иметь return type
     public function remove(CategoryInterface $category){}; // тут тоже лучше иметь return type
}

Так будет тоже самое что и сейчас у вас, только репозитории работают отдельно, и сервис слой их обьеденяет с вашей логикой. Репозитории не должны знать о существовании других репозиториев.
Поскльку у вас несколько репозиториев, создайте DTO и все данные после того как они преобразованы киньте в DTO, так у вас бьудет один стандартный обьект, над которым вы сможете выполнять дальнейшую логику.
Ответ написан
@dmitriylanets
А почему бы работу со связями не возложить на ORM?
Ответ написан
@masev
Web developer
Не знаю, насколько это хорошо в архитектурном плане. Как вариант, можно, у сущности Product добавить методы-геттеры или методы-модификаторы, которые будут дергать нужные репозитории.
$product = $productRepository->findOne(100);
$product->getCategories(); // Здесь по принципу lazy load ищем в нужном репозитории
$product->removeCategories();

В результате, так по цепочке можно связываться с нужными хранилищами.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы
Hearst Shkulev Digital Челябинск
от 50 000 до 100 000 руб.
Аскон Санкт-Петербург
от 130 000 до 180 000 руб.
Реформа Москва
от 70 000 руб.
21 апр. 2019, в 21:02
1000 руб./за проект
21 апр. 2019, в 19:41
5000 руб./за проект
21 апр. 2019, в 19:20
3500 руб./за проект