Как повысить поддерживаемость кода с помощью сервис-тегов в Symfony

от автора

Среди всего прочего в Leaseweb мы предлагаем нашим пользователям сервис Private Network, который позволяет им создать свою собственную частную сеть между другими продуктами Leaseweb.

Для решения задачи добавления оборудования, такого как серверы, в Private Network наша команда производственно-технического обеспечения использует класс под названием AddEquipmentService. Изначально мы поддерживали только серверы, но позже добавили поддержку колокации (Colocation), а за последние несколько месяцев добавили поддержку еще нескольких типов оборудования. Приоритетом для нашей команды является как можно более быстрая доставка решений для бизнеса, поэтому мы продолжали использовать и расширять тот же класс.

Поддерживаемые в настоящее время типы оборудования включают выделенные серверы (Dedicated Servers), выделенные серверные стойки (Dedicated Racks), колокацию, эластичные вычисления (Elastic Compute) и облако (Cloud). Со временем этот класс стал очень большим и сложным в обслуживании, так как он поддерживает сразу несколько типов оборудования, а каждый тип имеет свою бизнес-логику. Чтобы вернуть хоть какую-нибудь степень обслуживаемости нашему коду и сделать поддержку новых типов оборудования, таких как хранилища данных (Dedicated Storage), брандмауэры (Firewalls) и балансировщики нагрузки (Load Balancers), не такой болезненной, мы решили провести рефакторинг кода с использованием сервис-тегов (фичей фреймворка Symfony) в сочетании с паттерном проектирования программного обеспечения стратегия.

В рамках этого рефакторинга мы создали интерфейс с двумя методами: addEquipment и support. Метод addEquipment используется для добавления оборудования в частную сеть клиента, в то время как метод support проверяет, подходит ли данный тип оборудования для конкретного класса, реализующего интерфейс.

<?php  namespace App\Service;  use App\Entity\PrivateNetwork; use App\Entity\PrivateNetworkEquipment; use App\Service\EquipmentService\Equipment;  interface AddEquipmentInterface {     public function addEquipment(PrivateNetworkEquipment $privateNetworkEquipment, PrivateNetwork $privateNetwork, Equipment $equipment): PrivateNetworkEquipment;      public function supports(string $equipmentType): bool; }

Мы также создали класс-сервис (служебный класс) для каждого поддерживаемого типа оборудования, такого как выделенные серверы, выделенные серверные стойки, коллокации, эластичные вычисления и облако, о которых мы уже упоминали выше. Каждый класс-сервис реализует AddEquipmentInterface и содержит логику для добавления определенного типа оборудования в частную сеть.

<?php  // Класс-сервис, который мы создали для поддержки сервера  namespace App\Service;  use App\Entity\PrivateNetwork; use App\Entity\PrivateNetworkEquipment; use Doctrine\ORM\EntityManagerInterface; use App\Processor\QueueProcessor; use App\Service\EquipmentService\Equipment; use Psr\Log\LoggerInterface;  class AddServerToPrivateNetworkService implements AddEquipmentInterface {     protected $logger;     protected $queueProcessor;     protected $om;      public function __construct(LoggerInterface $logger, QueueProcessor $queueProcessor, EntityManagerInterface $om)     {         $this->logger = $logger;         $this->queueProcessor = $queueProcessor;         $this->om = $om;     }      public function addEquipment(PrivateNetworkEquipment $privateNetworkEquipment, PrivateNetwork $privateNetwork, Equipment $server)     {         // Логика добавления сервера в частную сеть...     }      public function supports(string $equipmentType): bool     {         return PrivateNetworkEquipment::EQUIPMENT_TYPE_DEDICATED_SERVER === $equipmentType;     } }
<?php  // Класс-сервис, который мы создали для поддержки колокации   namespace App\Service;  use App\Entity\PrivateNetwork; use App\Entity\PrivateNetworkEquipment; use Doctrine\ORM\EntityManagerInterface; use App\Processor\QueueProcessor; use App\Service\EquipmentService\Equipment; use Psr\Log\LoggerInterface;  class AddColocationToPrivateNetworkService implements AddEquipmentInterface {     protected $logger;     protected $queueProcessor;     protected $om;      public function __construct(LoggerInterface $logger, QueueProcessor $queueProcessor, EntityManagerInterface $om)     {         $this->logger = $logger;         $this->queueProcessor = $queueProcessor;         $this->om = $om;     }      public function addEquipment(PrivateNetworkEquipment $privateNetworkEquipment, PrivateNetwork $privateNetwork, Equipment $colocation)     {         // Логика добавления колокации в частную сеть...     }      public function supports(string $equipmentType): bool     {         return PrivateNetworkEquipment::EQUIPMENT_TYPE_COLOCATION === $equipmentType;     } }

Тегирование интерфейса в services.yaml

Все классы-сервисы, которые являются инстансами AddEquipmentInterface будут протегированы автоматически, что на практике означает, что классы, которые мы определили выше, могут быть извлечены из сервис-контейнера (Service Container) без предварительного связывания их вручную.

services:   _instanceof:     App\Service\AddEquipmentInterface:       tags: [ 'app.add_equipment_interface' ]

Внедрение сервиса в класс контекста

Далее мы создадим класс контекста AddEquipmentService. Нам нужно будет перебрать множество сервисов, чтобы найти, какие из них подходят для добавления оборудования в частную сеть.

Мы можем легко внедрить эти сервисы в конструктор класса, указав в services.yaml имя переменной и тег интерфейса, который мы установили ранее.

App\Service\AddEquipmentService:     arguments:       $addEquipmentServices: !tagged_iterator app.add_equipment_interface

Создание класса контекста

Класс контекста AddEquipmentService не содержит каких-либо функций для прямого добавления оборудования в частную сеть. Вместо этого, чтобы найти правильный класс, он будет использовать метод supports() каждого сервиса.

<?php  namespace App\Service;  use App\Entity\PrivateNetwork; use App\Entity\PrivateNetworkEquipment; use App\Exception\BadRequestException; use App\Service\EquipmentService\Equipment; use Psr\Log\LoggerInterface;  class AddEquipmentService {     /**      * @var AddEquipmentInterface[]      */     public iterable $addEquipmentServices;      protected $auditLogger;      public function __construct(LoggerInterface $auditLogger, iterable $addEquipmentServices)     {         $this->auditLogger = $auditLogger;         $this->addEquipmentServices = $addEquipmentServices;     }      public function addEquipment(PrivateNetworkEquipment $privateNetworkEquipment, PrivateNetwork $privateNetwork, Equipment $equipment, int $desiredLinkSpeed, bool $allServers = false): PrivateNetworkEquipment     {         foreach ($this->addEquipmentServices as $addEquipmentService) {             if ($addEquipmentService->supports($equipment->getType())) {                 $privateNetworkEquipment = $addEquipmentService->addEquipment($privateNetworkEquipment, $privateNetwork, $equipment);                  $this->auditLogger->info('private_network_equipment_added', [                     'customerId' => $privateNetwork->getCustomerId(),                     'equipmentId' => $privateNetworkEquipment->getEquipmentId(),                     'equipmentType' => $privateNetworkEquipment->getEquipmentType(),                 ]);                  return $privateNetworkEquipment;             }         }          throw new BadRequestException("Equipment {$equipment->getType()} is not supported");     } }

Класс контекста также отвечает за общую функциональность типов оборудования, например за создание событий журнала аудита.

Заключение

Структура каталогов

Рефакторинг класса AddEquipmentService позволил команде разработчиков сделать код более удобным для сопровождения и легким в добавлении поддержки новых типов оборудования. Мы можем просто создать новый сервис-класс, реализующий addEquipmentInterface. Добавление контекстного класса AddEquipmentService позволило находить правильный сервис-класс для использования и обработки служебных функций среди доступных типов оборудования. В целом, этот рефакторинг, вероятно, приведет к более легкому  и эффективному процессу добавления оборудования в частную сеть.


Приглашаем всех желающих на открытое занятие «Мониторинг исключений с помощью Graphite и Grafana». Что участников ожидает на уроке:
— сбор статистики с помощью Graphite,
— построение графиков и настройка алертов в Grafana,
— как обрабатывать исключения, которые не являются ошибками

Регистрация на вебинар открыта на странице онлайн-курса «Symfony Framework».


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/713080/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *