Среди всего прочего в 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/
Добавить комментарий