Каждый разработчик рано или поздно сталкивается с легаси-код — тем самым старым, не всегда понятным и, порой, пугающим набором строк, в которых легко заблудиться. Легаси может вызывать смешанные чувства: с одной стороны, это результат чьей-то работы и часть истории компании, с другой — постоянный вызов, требующий не просто поддержки, но и регулярных улучшений. Переделать такой код в «конфетку» — задача не из лёгких, но вполне выполнимая.
В моем случае код перешел к нам в процессе передачи проекта с аутсорсинг в внутреннюю разработку. В таких ситуациях нередко возникают сложности: передаваемый код может быть плохо документирован, содержать временные решения и не соответствовать стандартам компании. Часто бывает что это результат работы нескольких команд, и каждая могла вносить свои изменения, не задумываясь о долгосрочной поддержке. В итоге мы получили классический пример легаси — набор кода, который требовал тщательного анализа и серьезной переработки, чтобы стать действительно работоспособной и поддерживаемой системой.
В этой статье я поделюсь опытом того, как наша команда подошла к обновлению и модернизации проекта, на что у нас ушел целый год.
Следует отметить, что я пишу эту статью спустя год после завершения проекта, и возможно, какие-то моменты уже стерлись из памяти или остались за кадром.
🔖 Словарь
Устаревший код — код, написанный на технологиях, которые больше не поддерживаются или считаются неэффективными. Такой код сложно модифицировать или интегрировать с новыми решениями.
Технический долг — это код, который был создан для быстрого решения задачи, без учета долгосрочной поддержки и качества. Он требует переделки или реффакторинга, чтобы не стать препятствием для развития продукта.
Код без тестов — легаси-код также часто называют код, который не имеет покрытия тестами. Это делает его сложным и рискованным для изменений, так как нет гарантии, что изменение в одной части кода не нарушит работу другой.
«Ball of mud» — термин для описания кода, который настолько запутан, что не имеет четкой структуры и логики. Такие проекты со временем превращаются в хаотичную систему, где каждый новый модуль или функция усиливает эту запутанность.
Неоптимизированный код — код, который выполняет свою задачу, но сделан неэффективно, занимает много ресурсов и времени на выполнение.
Код, который быстро устаревает — иногда легаси-код становится даже тот, что был написан недавно. Технологии и требования к продукту меняются так быстро, что даже качественный код, написанный месяц назад опытным разработчиком, может стать «легаси». Это происходит, когда подходы, используемые в коде, уже не соответствуют новым стандартам или бизнес-целям.
Код, который сложно поддерживать — код не документирован или его логика слишком сложна для понимания, он превращается в легаси, даже если он создан опытными разработчиками
🎢 Процесс переход проекта
Формирование внутренней команды
В ходе обсуждения с руководством было принято решение выделить отдельную команду для перевода проекта с аутсорсинг на внутреннюю разработку, а также для дальнейшей поддержки и развития проекта.
-
Определение необходимых ролей: Tech Lead, Tech Writer, PM, DevOps, 3-BackEnd Разработчика, 2-QA.
-
Набор сотрудников: Проведение собеседований, оценка навыков и опыта кандидатов.
-
Привлечение внутренних сотрудников: временный перевод одного FrontEnd специалиста c другой команды.
Обучение: Обучение новых сотрудников особенностям проекта, технологиям, процессам.
Создание плана перехода
-
Определение сроков выполнения
Проект разделен на 3 этапы с конкретными сроками, общий срок — 3 месяца. Это позволило контролировать прогресс и вовремя адаптироваться при необходимости. -
Бизнес должен функционировать: все текущие задачи должны выполняться эффективно и в поставлены срок.
-
Обеспечение бесшовного перехода
Проведение всех изменений так, чтобы конечные пользователи не заметили изменений и не столкнулись с проблемами в работе. Это позволяет минимизировать риски для опыта пользователя.
-
Создание общего чата и среды для общения
Установка совместного канала для оперативного общения и обмена информацией между командами, чтобы поддерживать доступность и синхронизацию. -
Определение возможных рисков и стратегий их минимизации
Включение анализа потенциальных проблем — таких как несогласованность кода или конфликт подходов, с выработкой стратегий минимизации рисков. На это выделено 1 месяц для основательного анализа и подготовки. -
Регулярные тех-звонки внутри команды
Раз в неделю договорились проводить встречи для обсуждения текущих задач, выявления трудностей и согласования работы. -
Звонки для синхронизации с командами
Раз в неделю договорились о общих встречи с обеими командами для обмена информацией о статусе, текущих проблемах и достижениях. Это поможет избежать недоразумений и упростит принятие решений. -
Совместная разработка в одной кодовой базе
Понимание, что некоторое время обе команды будут работать вместе. Несмотря на то, что это могло вызвать определенное напряжение, для успешного завершения перехода был необходим период совместной разработки. В течение 1 месяца изменений в коде не можно было производить, чтобы обеспечить стабильность передаваемой базы. -
Организация бэкап и перенос инфраструктуры 1-к-1
Проведение резервного копирования всех систем и данных, а также обеспечение переноса инфраструктуры в новую среду с минимальными простоями. Этот этап помогает защитить данные и обеспечить их доступность на новом месте.
-
Перенос задач в Jira: Создания нового проект в Jira для внутренней команды или изменение существующего проекта, чтобы он соответствовал требованиям команды. Перенесите задачи и эпики из текущего проекта
-
Перенос файлов пользователя через CDN: разработка стратегии для их переноса на внутренние сервера или новую CDN
-
Обновление всех паролей и доступов: Проведения аудита текущих паролей и учетных записей, используемых в проекте, включая базы данных, API, сторонние сервисы и инструменты …
-
Подготовка финальной документации и руководство по работе с кодом
Упорядоченная и детализированная документация для команды, чтобы переход был максимально комфортным, а дальнейшая поддержка — успешной и предсказуемой. -
Подведение итогов
Проведения ретро для обсуждение полученного опыта и возможностей улучшения в будущих переходах.
Анализ текущего состояния проекта
-
Инфраструктура: До того, как в проект вступила команда разработки, первым весь масштаб проблемы осознал наш DevOps. За 1 месяц до того, как мы увидели код, он начал изучение текущей инфраструктуры, зависимостей, серверов и баз данных, чтобы подготовить основу для будущей работы команды.
-
Оценка кодовой базы: Получив доступ к кодовой базе ты на самом деле понимаешь с чем тебе придется иметь дело. Пришлось провести глубокий анализ существующего кода, определить выявление технический долг, найти архитектурные проблем. Об всем об этом я подробнее расскажу дальше.
-
Документация: Думаю, вы уже догадались, что будет написано в этом пункте. Документации, конечно же, не оказалось. Все, что удалось найти, — это нерабочие коллекции Postman, которые лишь добавили хаоса в и без того непростую задачу. Важную роль в процессе перехода сыграл Tech Writer. В течение всего проекта он тщательно собирал информацию о работе всех процессов и создавал документацию. Его усилия помогли систематизировать знания, обеспечив доступ к необходимой информации для команды и облегчая адаптацию к новым условиям. Благодаря его работе мы смогли избежать потери важной информации и улучшить понимание процессов внутри команды.
-
Процессы: Проанализировав текущие процессы разработки, мы поняли, что они нам не подойдут. Существующие процессы, мягко говоря, были не структурированны: отсутствовали четкие регламенты по методологии работы, тестированию, развертыванию, а также стандартам используемых инструментов. Найти ответственных за те или иные задачи было крайне сложно.
-
Команда: По нашей оценке, квалификация и опыт команды разработчиков на аутсорсинг соответствовали уровню начинающего джуну, тогда как по документам компания нанимала сотрудников уровня strong senior.
Внедрение процессов
-
Внедрение Agile-методологии разработки: Первое что нужно было сделать это выбрать подходящую Agile-методологию (Scrum, Kanban, или другие), которая будет соответствовать потребностям команды и проекту. Мы выбрали — Scrum.
-
Установление стандартов кодирования: Были определены стандарты кодирования, включая правила оформления кода, именования переменных и написания комментариев, чтобы обеспечить единообразие и читаемость кода
-
Автоматизация: Внедрены плагины(Jira, Git), инструментов автоматизации тестирования, развертывания, мониторинга.
-
Системы контроля версий: Переход на единую систему контроля версий (Git, SVN и т.д.). Выбор Git-flow как основная модель работы с Git.
-
Назначения инцидент-менеджера: Человек с этой ролью должен отвечать за оперативное управление инцидентами, выявление и устранение критических проблем.
-
Построения тех бэклога: Составления технического бэклога, включающий текущие задачи и необходимые улучшения на основе анализа кода и требований бизнеса.
-
Организация процесса релизов: Установка формата и процесса релизов и информирования команды о предстоящих изменениях, чтобы обеспечить готовность к обновлениям и минимизировать возможные проблемы.
Ретроспектива
В течение 3 месяцев мы столкнулись с различными вызовами, которые повлияли на наш переход к внутренней разработке. Совместный анализ поможет нам понять, что сработало, а что можно улучшить в будущем, чтобы обеспечить более эффективную работу команды и достичь поставленных целей.
-
Сроки: Сроки проекта были соблюдены — 3 месяца.
-
Переход: Бесшовный переход не был достигнут, что привело к определённым сложностям в адаптации команды к новым процессам.
-
Инциденты: Инциденты происходили каждый день, что указывает на недостатки в тестировании и контроле качества перед релизом.
-
Процесс релиза: Процесс релиза оставался на половину ручным, что увеличивало риск ошибок и замедляло развертывание обновлений.
-
DevOps: DevOps не прошел испытательный срок и был уволен, что затруднило внедрение автоматизации и улучшение процессов.
-
Команда: Команда была хорошо собрана и дружная, что способствовало открытой коммуникации и сотрудничеству.
-
Домен: Плохие знания домена, срок на выполнения задач приходилось увеличивать x-2
-
🏊 Погружения
Проект перенесен на внутреннюю разработку, и теперь начинается следующая фаза — поддержка и развитие. Разумеется, в текущем состоянии выполнить это было бы крайне сложно или практически невозможно.
Погружение в код (хотелось бы тут использовать другое слово) — этот процесс включает в себя анализ существующего кода, выявление его сильных и слабых сторон, а также определение областей для улучшения. Понимание домена позволило нам не только быстрее решать возникающие проблемы, но и вносить изменения с учетом лучших практик и стандартов разработки.
Докеризация
Для удобного развёртывание проекта на любой ОС разработчикам, было сделано:
-
Создан файл docker-compose.yml
-
Создан Makefile для автоматизации команд
-
Создан install.sh для быстрого поднятия проекта
-
Создана инструкция в README.md
-
Установка Docker и Docker Compose.
-
Шаги для сборки и запуска проекта с помощью команд из Makefile.
-
Описания проблем и как их можно исправить
-
Следуя этим шагам, каждый разработчик сможет легко поднять проект, независимо от используемой операционной системы.
Git
Для организации работы с Git были разработаны и внедрены следующие правила, обеспечивающие упорядоченный и эффективный рабочий процесс:
Переход на Gitflow
Gitflow — это подход к управлению ветками, который помогает структуировать процесс разработки. Основными ветками являются main (или master) для стабильных версий и develop для активной разработки.
Сквош перед мержем в develop
Перед тем как сливать изменения в ветку develop, рекомендуется выполнять сквош (squash) — объединение нескольких коммитов в один. Это помогает сохранить историю develop чистой и лаконичной, улучшая читаемость и упрощая отслеживание изменений, особенно в случае долгих и комплексных разработок.
Правила именования веток
Новые функции, исправления багов и релизные версии разрабатываются в отдельных ветках (feature/*, bugfix/, hotfix/*, release/*), что облегчает управление версиями и отслеживание изменений.
Коммиты: Каждый коммит должен содержать одно логическое изменение и быть снабжен понятным сообщением, описывающим суть изменений. Для единого стиля был написан вебхук:
#!/bin/bash FILE=$1 MESSAGE=$(cat $FILE) TICKET=$(git rev-parse --abbrev-ref HEAD | grep -Eo '^(\w+/)?(\w+[-_])?[0-9]+' | grep -Eo '(\w+[-])?[0-9]+' | tr "[:lower:]" "[:upper:]") if [[ -z "$TICKET" || "$MESSAGE" == "$TICKET"* ]]; then exit 0 fi NEW_MESSAGE="$TICKET: $MESSAGE" echo "$NEW_MESSAGE" > $FILE
Именование Merge Request (MR): Merge Request должны именоваться по установленным правилам с использованием номера задачи из JIRA + ее описания (например AMG-33: названия задачи). Это поможет связать изменения с соответствующими задачами и упростить отслеживание.
Тех документация
Была создата тех документация. Она дала команде более четкое Понимание системы, помогла разработчикам, инженерам понять, как работает система, какие у нее возможности и ограничения. Дала инструкции по установке, настройке, архитектуры и кода. Служила руководством не только для пользователей, но и для разработчиков, помогая им освоить новые системы и программы.
Логирование
Для оптимизации логирование в проекте были разработаны и внедрены следующие правила:
-
Приведение логов к единому формату
Определен стандартный формат логов, включающий основные поля: временные метки, уровни логирование (info, error, warning), идентификаторы запросов и другую важную информацию. Это позволяет легко анализировать логи, искать ошибки и понимать последовательность событий. Унифицированный формат делает логи более структурированным и удобными для поиска. -
Удаление ненужных и добавление необходимых логов
Проведен аудит существующих логов: удалены избыточные и дублирующие записи, а также добавлены важные логи, необходимые для отслеживания ключевых действий и процессов. Это помогает сократить объем хранимых данных, делая логи более лаконичными и информативными, а также улучшает производительность системы. -
Логирование запросов
Включено логирование всех входящих и исходящих запросов/ответов, чтобы отслеживать каждый этап взаимодействия с системой. Это особенно полезно для диагностики проблем и анализа поведения системы в случае ошибок. Логирование запросов и ответов включает информацию о заголовках, URL, параметрах запроса и статусах ответов, что помогает быстро находить и исправлять ошибки в интеграциях и API. -
Разделение каналов логов по уровням и типам
Создайте отдельные каналы для различных типов логов, таких как info, error, debug, warning, и critical. Это позволит более гибко управлять уровнем детализации и быстро фильтровать логи по важности. -
Создание отдельных каналов для ключевых компонентов
Разделите логи по каналам, отражающим основные компоненты системы: request, response, database, auth, payment, console, api и т.д. Это позволяет сосредоточиться на логах конкретного компонента, упрощая анализ и диагностику проблем.
Эти правила логирование помогли поддерживать структуру, информативность и полезность логов, что значительно облегчает мониторинг, диагностику и оптимизацию работы системы.
Архитектура Приложения
Хочу сказать что внедрения многослойной архитектуры, был подготовкой к переходу на DDD архитектуру с ориентацией на домен. Было бы очень сложно сделать такой скачек без знания доменов и их границ сразу.
Внедрение многослойной архитектуры помогло структурировать проект, улучшить читаемость кода и упростило его поддержку. Переход от хаотичной организации папок к многослойной архитектуре включало несколько шагов.
Выделение основных слоев
В зависимости от потребностей проекта можно выделить несколько ключевых слоев. Наиболее распространенные:
-
Presentation (или Interface) Layer — содержит логику, связанную с интерфейсом пользователя (например, контроллеры API и веб-контроллеры).
-
Application Layer — отвечает за оркестрацию бизнес-логики, обработку команд, вызов нужных сервисов, но не содержит бизнес-логику.
-
Domain (или Business) Layer — включает бизнес-логику и основные правила. Здесь находятся сущности, интерфейсы и другие компоненты, описывающие бизнес-процессы.
-
Infrastructure Layer — отвечает за работу с внешними системами и ресурсами: базы данных, файловые системы, внешние API. Этот слой взаимодействует с технологическими аспектами системы.
Организация cтруктуры кода
Организовывайте папки в проекте согласно выбранным слоям:
Переход на ADR
Переход на паттерн ADR (Action-Domain-Responder) стал ключевым шагом для улучшения структуры кода и повышения его читаемости. Вместо крупных контроллеров, содержащих много логики, мы внедрили следующий подход:
-
Разделение ответственности
В рамках ADR мы разделили логику приложения на три основные компоненты:-
Action (Действие): обрабатывает входящие запросы и взаимодействует с доменом. Этот компонент отвечает за извлечение необходимых данных и выполнение бизнес-логики.
-
Domain (Домен): содержит бизнес-логику и модели, включая правила и валидацию данных. Доменная модель остается независимой от внешних технологий и инфраструктуры.
-
Responder (Ответчик): формирует ответ для клиента. Этот компонент отвечает за представление данных и может включать логику для формирования различных форматов ответа, таких как JSON или HTML.
-
-
Сокращение кода в контроллерах
С переходом на ADR наши контроллеры стали более легковесными и специализированными. Каждый контроллер теперь выполняет только функции, связанные с маршрутизацией и передачей управления между Action и Responder, что упрощает код и делает его более читаемым. -
Упрощение тестирования
Благодаря разделению логики на три компонента, тестирование стало более управляемым. Мы можем тестировать каждую часть (Action, Domain, Responder) отдельно, что упрощает процесс отладки и повышает надежность кода. -
Гибкость и расширяемость
Структура ADR позволяет легче добавлять новые действия и изменять существующие, поскольку изменения в одном компоненте не влияют на другие. Это делает код более адаптируемым к изменениям бизнес-требований. -
Улучшение понимания кода
Четкое разделение компонентов по их обязанностям делает код более понятным для новых разработчиков, поскольку они могут быстро ориентироваться в структуре приложения и понимать, где искать определенную функциональность.
Работа с Application Layer
Использование CQS и подхода с Use Case в Application Layer значительно улучшила архитектуру нашего приложения. Это позволило разделить команды чтения и записи, что помогло повысить производительность и упростить управление бизнес-логикой.
Работа с Infrastructure Layer
Для улучшения работы с инфраструктурой проекта и обеспечения единых подходов, рекомендуется следующее:
-
Работа с очередями
Чтобы структура сообщений в очереди была предсказуемой и удобной для обработки, внедрите единый формат сообщений. Прослойка служит посредником между логикой приложения и конкретной реализацией очередей. Она абстрагирует детали реализации, позволяя разработчикам сосредоточиться на бизнес-логике. -
Доступ к БД через Repository патерн
Репозитории выполняют операции с данными и предоставляют интерфейсы для работы с конкретными сущностями.
-
Интерфейсы: создайте интерфейсы для каждого репозитория, которые будут описывать методы доступа к данным. Это обеспечит независимость кода от конкретной реализации доступа к БД и позволит легко изменять реализацию при необходимости (например, при замене базы данных или использовании mock-объектов для тестирования).
-
Репозитории: реализуйте интерфейсы в репозиториях, которые будут выполнять операции с базой данных через ORM или SQL-запросы. Это повысит гибкость кода и упростит тестирование и внедрение изменений.
-
Прослойка для MessageBus
Для работы с шиной сообщений (MessageBus) добавили промежуточный слой, который будет управлять отправкой и обработкой сообщений. Этот слой выполняет роль посредника между логикой приложения и фактическими вызовами MessageBus:-
Управлять сериализацией и десериализацией сообщений.
-
Обрабатывать исключения и логгировать ошибки.
-
Поддерживать единый формат сообщений и обеспечивать унифицированные интерфейсы для отправки сообщений.
-
Прослойка также упрощает тестирование, поскольку можно использовать mock- или stub-реализации MessageBus для имитации поведения системы при тестировании бизнес-логики.
-
Переход на EightPointsGuzzle
Библиотека EightPointsGuzzle предоставила удобный интеграционный слой с Guzzle для Symfony, предлагая стандартные middleware для логгирования, обработки ошибок и отслеживания запросов. Использование этого пакета позволяет:-
Снизить сложность работы с внешними API за счет единого клиента для HTTP-запросов.
-
Настроить общие middleware для всех запросов, такие как аутентификация, ретраи и таймауты.
-
Легко интегрировать с Symfony-сервисами и конфигурацией, что упрощает подключение и настройку HTTP-клиента.
-
Переход на EightPointsGuzzle позволяет унифицировать работу с внешними API, улучшить логгирование и повысить надежность запросов за счет встроенных механизмов обработки ошибок.
Эти подходы помогли стандартизировать работу с инфраструктурным слоем, улучшить качество кода и упростить поддержку проекта.
Работа с Domain
По мере изучения домена вместе с командой начали формироваться субдомены и выделяться ограниченные контексты (bounded contexts). Это позволило более точно определить границы ответственности для каждого модуля, улучшить структурирование бизнес-логики и упростить работу с отдельными компонентами системы. В результате каждый субдомен стал представлять отдельную часть предметной области, со своими правилами, сущностями и взаимодействиями, что облегчает как поддержку, так и развитие проекта.
Инъекция зависимостей
Внедрение инъекции зависимостей (DI) в нашем проекте значительно улучшило управление зависимостями и сделало код более гибким и тестируемым. Вот основные шаги и принципы, которые были внедрены:
-
Доступ к сервисам через DI
Вместо того чтобы получать сервисы напрямую из контейнера, мы используем инъекцию зависимостей для передачи необходимых сервисов в классы через конструкторы или методы. Это позволяет:-
Ясно видеть, какие зависимости необходимы каждому компоненту.
-
Упрощать тестирование, так как можно легко подменять зависимости на mock-объекты.
-
-
Доступ к параметрам через DI
Параметры конфигурации теперь также передаются через инъекцию зависимостей. Это позволяет избежать жесткой привязки к контейнеру и делает классы более независимыми. В результате:-
Компоненты становятся более модульными и переиспользуемыми.
-
Упрощается управление настройками и конфигурациями, так как все параметры можно передать при создании объекта.
-
-
Запрет на использование контейнера
Мы запретили использование контейнера в классах и методах. Это означает, что компоненты не могут напрямую обращаться к контейнеру для получения зависимостей. Такие меры позволили:-
Исключить зависимость от контейнера, что облегчает тестирование и делает код более чистым.
-
Повысить уровень абстракции, поскольку классы не знают о том, как управляются их зависимости.
-
Упростить миграцию на другие реализации DI, если это станет необходимым в будущем.
-
Инъекция зависимостей позволяет создать более поддерживаемую и гибкую архитектуру, что в свою очередь способствует улучшению качества кода и повышению производительности команды разработки.
Безопасность
Для повышения уровня безопасности в нашем проекте были предприняты следующие шаги:
-
Переход от параметров к окружению (env)
Мы заменили использование параметров конфигурации на переменные окружения. Это позволяет хранить чувствительную информацию, такую как ключи API и пароли, вне кода и конфигурационных файлов, что значительно снижает риск их утечки. -
Удаление паролей из репозитория
Все пароли и конфиденциальные данные были удалены из репозитория. Вместо этого мы используем переменные окружения для их хранения. Это обеспечит защиту информации и предотвратит случайное раскрытие данных при работе с системой контроля версий. -
Перенос SQL-запросов из кода в репозитории
SQL-запросы были вынесены в отдельные файлы или репозитории. Это не только улучшает структуру кода, но и позволяет применять лучшие практики, такие как использование миграций для управления схемой базы данных и данных. -
Использование шифрования для конфиденциальной информации
Внедрение шифрования для хранения и передачи конфиденциальной информации. Это позволяет защитить данные даже в случае их компрометации. Например, можно использовать библиотеки для шифрования паролей и хранения токенов. -
Регулярный аудит безопасности кода
Мы начали проводить регулярные аудиты безопасности кода, используя статические анализаторы и инструменты для проверки уязвимостей. Это помогает выявлять потенциальные проблемы до того, как они станут серьезными угрозами. -
Аутентификация и авторизация
Мы улучшили механизмы аутентификации и авторизации, внедрив двухфакторную аутентификацию (2FA) и обновив политику доступа к ресурсам. Это обеспечивает дополнительный уровень защиты и предотвращает несанкционированный доступ. -
Логирование и мониторинг
Внедрение систем логирования и мониторинга для отслеживания подозрительной активности и потенциальных угроз безопасности. Это позволяет своевременно реагировать на инциденты и проводить расследования. -
Удаление секретов из логов
Внедрение механизма для фильтрации и удаления конфиденциальной информации (например, паролей и токенов) из логов. Это предотвращает возможность несанкционированного доступа к чувствительным данным, которые могут быть случайно записаны в журналы.
Эти меры значительно улучшили безопасность нашего проекта, снизили риски и защитили конфиденциальные данные.
Рефакторинг
В процессе оптимизации кода и повышения его качества были предприняты следующие шаги:
-
Удаление нерабочих тестов
Все нерабочие и устаревшие тесты были удалены, что позволило улучшить качество тестового покрытия и повысить уверенность в стабильности кода. Это также освободило время для написания новых тестов, охватывающих актуальные функциональности. -
Удаление лишних команд и консюмеров
Были удалены неиспользуемые команды и консюмеры, которые больше не имели практического применения. Это улучшило читаемость кода и снизило его сложность, что облегчает дальнейшую разработку и сопровождение. -
Удаление мертвого кода и комментариев
Весь мертвый код и устаревшие комментарии были удалены, что сделало код более чистым и понятным. Это способствует лучшему восприятию логики приложения и облегчает его поддержку новыми разработчиками. -
Рефакторинг и упрощение структуры кода
Проведен рефакторинг существующего кода с целью упрощения его структуры и улучшения читаемости. Это включает в себя разбивку больших функций на более мелкие, переименование переменных и классов для большей ясности. -
Обновление Swagger документации
Мы провели полное обновление документации Swagger для нашего API. Это включает в себя добавление новых эндпоинтов, обновление описаний и примеров, а также исправление ошибок в существующих документах. Обновленная документация обеспечивает более точное представление функциональности API и упрощает работу разработчиков, которые его используют. -
Настройка коллекций Postman
Создали и настроили коллекции Postman для автоматизации тестирования API. Это позволяет команде легко выполнять запросы, тестировать новые функциональности и делиться ими с другими членами команды. Коллекции также включают тесты для проверки ответов API, что способствует более быстрому выявлению проблем и повышает качество кода. -
Переименование cron-задач
Провели ревизию cron-задач и переименовали (Добавили `cron:` перед каждой командой) их для большей ясности и соответствия общим стандартам именования. Это упрощает понимание назначения каждой задачи и облегчает их сопровождение. Переименование задач также помогает избежать путаницы, особенно в среде с большим количеством запланированных операций.
Улучшения производительности
Для улучшения производительности сайта, который изначально не был предназначен для высокой нагрузки, были предприняты следующие меры оптимизации:
-
Анализ текущего состояния производительности
Мы провели анализ текущей производительности сайта с помощью инструментов мониторинга, что позволило выявить узкие места и области для оптимизации. -
Оптимизация загрузки ресурсов
Были оптимизированы изображения, шрифты и другие статические ресурсы. Это включает сжатие изображений и использование современных форматов, таких как WebP, что уменьшает время загрузки страниц. -
Кэширование
Внедрение кэширования на уровне сервера и клиента. Использование заголовков кэширования и кэширования статических ресурсов значительно уменьшает количество запросов к серверу и ускоряет загрузку страниц для пользователей. -
Минификация и объединение файлов
CSS и JavaScript файлы были минифицированы и объединены для уменьшения числа HTTP-запросов и уменьшения общего размера загружаемых ресурсов. -
Использование CDN (Content Delivery Network)
Внедрение CDN для доставки статического контента, что помогает снизить время отклика за счет использования ближайших к пользователю серверов. -
Оптимизация базы данных
Проведена оптимизация запросов к базе данных, включая индексацию, удаление неиспользуемых данных и упрощение сложных запросов. -
Устранение дедлоков
Мы проанализировали и оптимизировали операции с базой данных для предотвращения дедлоков. Это включает в себя оптимизацию порядка выполнения транзакций и использование тайм-аутов, что позволяет избежать взаимных блокировок и улучшить общую производительность системы. -
Улучшение кода
Рефакторинг кода для устранения избыточности и повышения его эффективности. Это включает в себя удаление мертвого кода и оптимизацию алгоритмов, использующихся в приложении. -
Мониторинг производительности
Настроены системы мониторинга для отслеживания производительности сайта в реальном времени. Это помогает оперативно выявлять и устранять проблемы с производительностью. -
Тестирование под нагрузкой
Проведены тесты под нагрузкой для определения предельной производительности сайта и выявления точек, которые могут вызвать сбои при увеличении числа пользователей.
Эти меры позволили значительно улучшить производительность сайта, что делает его более устойчивым к увеличению нагрузки и улучшает опыт пользователя.
DB (MySQL)
Для оптимизации работы с базой данных и повышения её эффективности были выполнены следующие действия:
-
Стандартизация названий полей и таблиц
Мы привели названия полей и таблиц к единому стилю, используя camelCase для улучшения читабельности и последовательности. Это упрощает понимание структуры базы данных и помогает разработчикам быстрее ориентироваться в коде. -
Добавление внешних ключей
Проведена ревизия полей с идентификаторами, и добавили внешние ключи там, где они отсутствовали. Это помогло соблюсти целостность данных и повысить надежность взаимодействия между таблицами.
-
Единый стиль именования
Мы применили единый стиль именования для всех сущностей базы данных, что улучшает консистентность и упрощает дальнейшее сопровождение. Это особенно важно в больших проектах, где работают несколько разработчиков. -
Слияние миграций
Объединили более 1000 миграций в одну, что позволило упростить процесс обновления схемы базы данных в будущем. Это снизило вероятность ошибок при выполнении старых миграций и сделало процесс поднятия приложения более управляемым. -
Замена статических таблиц на ENUM
Мы заменили статические таблицы на ENUM, что улучшило производительность и упростило поддержку данных, особенно для полей с ограниченным числом значений. Это также уменьшает избыточность данных и повышает читаемость. -
Добавление индексов для частых запросов
Добавили индексы на поля, которые часто используются в запросах, что значительно увеличило скорость выполнения операций чтения. После анализа запросов были удалены устаревшие индексы, которые больше не использовались, что снизило накладные расходы на поддержку базы данных. -
Оптимизация запросов
Проведена оптимизация SQL-запросов, что позволило уменьшить время выполнения и нагрузку на базу данных. Это включает в себя переработку сложных запросов и использование JOIN вместо подзапросов там, где это возможно. -
Регулярный мониторинг производительности
Настроены инструменты мониторинга для отслеживания производительности базы данных, что позволяет оперативно выявлять узкие места и принимать меры для их устранения. Это способствует более стабильной работе приложения и лучшему обслуживанию пользователей.
NoSQL (MongoDB)
В скорее мы поняли что MongoDB был выбрат на хайпе. И не использовался по назначению. Поэтому мы составили план по уменьшению зоопарка технологий.
Оптимизация схемы данных: Удалили часть таблиц. Изменили оставшиеся таблицы для улучшения структуры данных и уменьшения дублирования.
Анализ текущего использования: Провели оценку, какие функции MongoDB используются в проекте и как их можно реализовать с помощью других баз данных, которые уже используются.
Обновление кодовой базы: Обновили код приложения, чтобы убрать зависимости от MongoDB.
🏫 Infrastructure
-
Включение VPN на внутренние ресурсы: Настройка VPN обеспечивает безопасный доступ к внутренним ресурсам компании для сотрудников. Это позволяет защитить данные и поддерживать конфиденциальность при работе с чувствительной информацией.
-
Миграция сервисов в облако Amazon: Ведется процесс миграции существующих сервисов в облачную инфраструктуру Amazon Web Services (AWS). Это дает возможность использовать масштабируемые ресурсы, повышать доступность приложений и оптимизировать затраты на обслуживание серверов.
-
Настройки репликации ключевых элементов: Установлены параметры репликации для критически важных данных и сервисов. Это гарантирует высокую доступность и отказоустойчивость, а также обеспечивает защиту от потери данных в случае сбоя.
-
Создание staging-сред для внутренней разработки: Созданы стейджи (staging environments) для тестирования и разработки новых функций. Это позволяет разработчикам безопасно проверять изменения, прежде чем они будут развернуты в продуктивной среде, минимизируя риски и обеспечивая стабильность работы приложения.
-
Настройка кластера для Redis: Развернут кластер Redis для повышения производительности и обеспечение отказоустойчивости кэширования данных. Это позволяет эффективно обрабатывать высокие нагрузки и обеспечивает быстрый доступ к часто запрашиваемой информации.
-
Миграция логов в Elasticsearch: Логи системы были перенесены в Elasticsearch для централизованного хранения и анализа. Это обеспечивает удобный доступ к данным, позволяет легко искать и визуализировать информацию о производительности и проблемах в системе.
-
Распределение логов и бизнес данных в Elasticsearch: Было поднято отдельную Elasticsearch для бизнес данных.
🔧 CI/CD
-
Постепенная настройка CI: Шаг за шагом добавляли интеграцию CI для повышения автоматизации процесса разработки (csfixer, phpstan (9-level), psalm, deptrac, unit/functional tests).
-
Уменьшение времени деплоя: Оптимизация процессов, направленная на сокращение времени, необходимого для развертывания приложений.
-
Объединение кнопки деплоя и билда: Объединение действий в одну кнопку для упрощения развертывания и сборки.
-
Вынесение миграций из деплоя в билд: Миграции были перенесены в процесс сборки, чтобы улучшить управление версиями.
-
Проблемы с blue/green развертыванием: Столкнулись с трудностями при реализации blue/green подхода для развертывания.
-
Включение стейджей на ночь: Запуск стейджей в ночное время для тестирования и разработки.
-
Снижение ресурсов на стейджах: Оптимизация использования ресурсов для экономии.
-
Настройка ночных крон-задач: Конфигурация автоматизированных задач на ночное время.
-
Интеграция Jira + GitLab: Связка систем для улучшения управления проектами и задачами.
-
Запрет коммитов в мастер: Установление политики, предотвращающей прямые коммиты в основную ветку.
-
Автопроставление тегов: Настройка автоматической маркировки версий при деплое.
-
Автопроставление версии релиза в Sentry: Автоматическое обновление информации о версии в Sentry.
-
Автобилд npm по версии изменений: Настройка автоматической сборки npm пакетов при изменениях на фронте.
-
Проблемы с нехваткой ресурсов: Постоянная нехватка ресурсов, решалась вертикальным скейлингом и поиском утечек в коде.
-
Настройка алертов: Конфигурация системы оповещений для мониторинга состояния приложений.
-
Вынос конфигурации крона и супервайзера в репозиторий: Организация конфигурации для управления задачами и процессами.
-
Обновление версии Kubernetes: Периодическое обновление для обеспечения стабильности и безопасности.
🏁 Выводы
Процесс трансформации легаси-проекта оказался насыщенным вызовами и неожиданными трудностями, как для команды, так и для бизнеса. Нам пришлось решать множество технических и организационных проблем, переосмысливать архитектуру и адаптировать старые решения под современные требования. Благодаря усилиям и упорству, нам удалось превратить проект в действительно «конфетку» — устойчивую, гибкую и удобную систему, готовую к дальнейшему развитию и поддержке.
Прошел ровно 1 год с момента перехода, и именно столько времени нам понадобилось для завершения трансформации. Все это время бизнес-задачи выполнялись параллельно с техническими изменениями, без остановок и без заметных неудобств для пользователей.
ссылка на оригинал статьи https://habr.com/ru/articles/861120/
Добавить комментарий