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

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

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

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

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

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

Войти через центр авторизации
Похожие вопросы