Реализация SOLID на примере

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Рассмотрим на простом и наглядном примере реализацию SOLID на Symfony. Будет так же ссылка на Github.

Допустим, нужно реализовать импорт товаров из внешнего сервиса. Получится примерно такой код:

namespace App\Service\Product\Import;

use App\Entity\Product\Product;
use App\Repository\Product\ProductRepository;
use Doctrine\ORM\EntityManagerInterface;

class ImportService
{
    private const SOURCE_PATH = 'http://somedomain.com/products/';

    public function __construct(
        private EntityManagerInterface $em,
        private ProductRepository $productRepository
    )
    {
    }

    public function import(): void
    {
        $em = $this->em;
        $productsData = json_decode(file_get_contents(self::SOURCE_PATH), true);
        $i = 0;
        foreach ($productsData as $productData) {
            $product = $this->productRepository->findOneBy(['sku' => $productData['sku']]);
            if (!$product) {
                $product = new Product();
            }

            $product->setSku($productData['sku']);
            $product->setName($productData['name']);
            //...set other fields

            $em->persist($product);

            $i++;
            if ($i % 100 == 0) {
                $em->flush();
                $em->clear();
            }
        }

        $em->flush();
        $em->clear();
    }
}

И обычно этого достаточно. Но пойдём дальше, сделаем вторую версию реализации. Попробуем отделить логику импорта от остальной логики:

namespace App\Utils\Importer;

use Doctrine\ORM\EntityManagerInterface;

class Importer
{
    public function __construct(
        private EntityManagerInterface $em,
    )
    {
    }

    public function import(
        ImportableRepositoryInterface $importableRepository,
        ImportableFactoryInterface $importableFactory,
        ImportMapperInterface $importMapper,
        ImportReceiverInterface $importReceiver,
        string $identityFieldName,
        int $blockSize = 100
    ): void
    {
        $em = $this->em;
        $importData = $importReceiver->receive();
        $i = 0;
        foreach ($importData as $importItemData) {
            $identityFieldValue = $importItemData[$identityFieldName];
            $importable = $importableRepository->findOneByImportIdentity($identityFieldValue);
            if (!$importable) {
                $importable = $importableFactory->create();
            }

            $importMapper->map($importable, $importItemData);
            $em->persist($importable);

            $i++;
            if ($i % $blockSize == 0) {
                $em->flush();
                $em->clear();
            }
        }

        $em->flush();
        $em->clear();
    }
}

Теперь вместо самого товара имеем ImportableInterface. Так же для получения данных извне имеем ImportReceiverInterface, для маппинга этих данных на сущность ImportMapperInterface, и интерфейс для создания сущности, фабрику. Но общая логика осталась такая же.

Это инверсия зависимости по отношению к логике импорта. Если рассматривать её как отдельный модуль, то получается все верно, это и есть предметная область для этого модуля. Но в контексте нашего приложения это менее важная деталь. Поэтому, нужна ещё одна инверсия зависимости.

Это сервис, содержащий логику импорта товара, предметную область:

namespace App\Service\Product\ImportV2;

class ImportService
{
    public function __construct(
       private ImporterInterface $importer
    )
    {
    }

    public function import(): void
    {
        $this->importer->import();
        //...do other things
    }
}

А вот уже конкретика:

namespace App\Service\Product\ImportV2\Importer;

use App\Entity\Product\Product;
use App\Utils\Importer\Importer as BaseImporter;
use App\Service\Product\ImportV2\ImporterInterface;
use App\Service\Product\EntityFactory\ProductFactory;

class Importer implements ImporterInterface
{
    public function __construct(
        private BaseImporter $importer,
        private Mapper $mapper,
        private Receiver $receiver,
        private ImportableProductRepository $productRepository,
        private ProductFactory $productFactory
    )
    {
    }

    public function import(): void
    {
        $this->importer->import($this->productRepository, $this->productFactory, $this->mapper,
            $this->receiver, Product::IMPORT_IDENTITY_FIELD, 200);
    }
}

Код остальных классов можно посмотреть в исходном коде на гитхаб. Как можно заметить, так же применился и принцип единой ответственности, принцип открытости-закрытости, принцип разделения интерфейсов, в общем, весь SOLID.

Конечно, это только пример, но в теории, такой компонент импорта может дальше быть развит и использоваться в разных проектах. Можно, например, внедрить в него использование Symfony Serializer, получение данных через REST API, чтение по блокам и другое.

Источник: https://habr.com/ru/articles/774842/


Интересные статьи

Интересные статьи

Масштабирование — это не просто увеличение мощности, это искусство эффективного управления ресурсами, чтобы удовлетворить растущие запросы пользователей без потери качества обслуживания. В телеграм-бо...
Недавно я переехала в Лондон и погрузилась в увлекательный квест по съему жилья в новом городе. За неделю мною было упущено около 30 классных (и не очень комнат), отправлено 200+ запросов на просмотр ...
Меня всегда очень интересовала довольно грустная ситуация с языком РНР. Из неказистого шаблонного движка для веб-страничек, к середине 2010-х он вырос в мощный, современный и аккуратный язык програм...
Всем привет. Меня зовут Нещадин Иван, и я расскажу про оптимизацию одного из микросервисов Авито на Go. История построена вокруг различных инструментов, которые доступ...
Мы уже не раз рассказывали про свой GitOps-инструмент werf, а в этот раз хотели бы поделиться опытом сборки сайта с документацией самого проекта — werf.io (его русскоязычная версия — ru.werf.io)....