Из легаси в конфетку: история трансформации

от автора


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

В моем случае код перешел к нам в процессе передачи проекта с аутсорсинг в внутреннюю разработку. В таких ситуациях нередко возникают сложности: передаваемый код может быть плохо документирован, содержать временные решения и не соответствовать стандартам компании. Часто бывает что это результат работы нескольких команд, и каждая могла вносить свои изменения, не задумываясь о долгосрочной поддержке. В итоге мы получили классический пример легаси — набор кода, который требовал тщательного анализа и серьезной переработки, чтобы стать действительно работоспособной и поддерживаемой системой.

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

Следует отметить, что я пишу эту статью спустя год после завершения проекта, и возможно, какие-то моменты уже стерлись из памяти или остались за кадром.

🔖 Словарь

Устаревший код — код, написанный на технологиях, которые больше не поддерживаются или считаются неэффективными. Такой код сложно модифицировать или интегрировать с новыми решениями.

Технический долг — это код, который был создан для быстрого решения задачи, без учета долгосрочной поддержки и качества. Он требует переделки или реффакторинга, чтобы не стать препятствием для развития продукта.

Код без тестов — легаси-код также часто называют код, который не имеет покрытия тестами. Это делает его сложным и рискованным для изменений, так как нет гарантии, что изменение в одной части кода не нарушит работу другой.

«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


🏊 Погружения

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

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

Докеризация

Для удобного развёртывание проекта на любой ОС разработчикам, было сделано:

  1. Создан файл docker-compose.yml

  2. Создан Makefile для автоматизации команд

  3. Создан install.sh для быстрого поднятия проекта

  4. Создана инструкция в 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: названия задачи). Это поможет связать изменения с соответствующими задачами и упростить отслеживание.

Тех документация

Была создата тех документация. Она дала команде более четкое Понимание системы, помогла разработчикам, инженерам понять, как работает система, какие у нее возможности и ограничения. Дала инструкции по установке, настройке, архитектуры и кода. Служила руководством не только для пользователей, но и для разработчиков, помогая им освоить новые системы и программы.

Логирование

Для оптимизации логирование в проекте были разработаны и внедрены следующие правила:

  1. Приведение логов к единому формату
    Определен стандартный формат логов, включающий основные поля: временные метки, уровни логирование (info, error, warning), идентификаторы запросов и другую важную информацию. Это позволяет легко анализировать логи, искать ошибки и понимать последовательность событий. Унифицированный формат делает логи более структурированным и удобными для поиска.

  2. Удаление ненужных и добавление необходимых логов
    Проведен аудит существующих логов: удалены избыточные и дублирующие записи, а также добавлены важные логи, необходимые для отслеживания ключевых действий и процессов. Это помогает сократить объем хранимых данных, делая логи более лаконичными и информативными, а также улучшает производительность системы.

  3. Логирование запросов
    Включено логирование всех входящих и исходящих запросов/ответов, чтобы отслеживать каждый этап взаимодействия с системой. Это особенно полезно для диагностики проблем и анализа поведения системы в случае ошибок. Логирование запросов и ответов включает информацию о заголовках, URL, параметрах запроса и статусах ответов, что помогает быстро находить и исправлять ошибки в интеграциях и API.

  4. Разделение каналов логов по уровням и типам
    Создайте отдельные каналы для различных типов логов, таких как info, error, debug, warning, и critical. Это позволит более гибко управлять уровнем детализации и быстро фильтровать логи по важности. 

  5. Создание отдельных каналов для ключевых компонентов
    Разделите логи по каналам, отражающим основные компоненты системы: 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) стал ключевым шагом для улучшения структуры кода и повышения его читаемости. Вместо крупных контроллеров, содержащих много логики, мы внедрили следующий подход:

  1. Разделение ответственности
    В рамках ADR мы разделили логику приложения на три основные компоненты:

    • Action (Действие): обрабатывает входящие запросы и взаимодействует с доменом. Этот компонент отвечает за извлечение необходимых данных и выполнение бизнес-логики.

    • Domain (Домен): содержит бизнес-логику и модели, включая правила и валидацию данных. Доменная модель остается независимой от внешних технологий и инфраструктуры.

    • Responder (Ответчик): формирует ответ для клиента. Этот компонент отвечает за представление данных и может включать логику для формирования различных форматов ответа, таких как JSON или HTML.

  2. Сокращение кода в контроллерах
    С переходом на ADR наши контроллеры стали более легковесными и специализированными. Каждый контроллер теперь выполняет только функции, связанные с маршрутизацией и передачей управления между Action и Responder, что упрощает код и делает его более читаемым.

  3. Упрощение тестирования
    Благодаря разделению логики на три компонента, тестирование стало более управляемым. Мы можем тестировать каждую часть (Action, Domain, Responder) отдельно, что упрощает процесс отладки и повышает надежность кода.

  4. Гибкость и расширяемость
    Структура ADR позволяет легче добавлять новые действия и изменять существующие, поскольку изменения в одном компоненте не влияют на другие. Это делает код более адаптируемым к изменениям бизнес-требований.

  5. Улучшение понимания кода
    Четкое разделение компонентов по их обязанностям делает код более понятным для новых разработчиков, поскольку они могут быстро ориентироваться в структуре приложения и понимать, где искать определенную функциональность.

Работа с Application Layer

Использование CQS и подхода с Use Case в Application Layer  значительно улучшила архитектуру нашего приложения. Это позволило разделить команды чтения и записи, что помогло повысить производительность и упростить управление бизнес-логикой.

Работа с Infrastructure Layer

Для улучшения работы с инфраструктурой проекта и обеспечения единых подходов, рекомендуется следующее:

  1. Работа с очередями
    Чтобы структура сообщений в очереди была предсказуемой и удобной для обработки, внедрите единый формат сообщений. Прослойка служит посредником между логикой приложения и конкретной реализацией очередей. Она абстрагирует детали реализации, позволяя разработчикам сосредоточиться на бизнес-логике.

  2. Доступ к БД через Repository патерн

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

  • Интерфейсы: создайте интерфейсы для каждого репозитория, которые будут описывать методы доступа к данным. Это обеспечит независимость кода от конкретной реализации доступа к БД и позволит легко изменять реализацию при необходимости (например, при замене базы данных или использовании mock-объектов для тестирования).

  • Репозитории: реализуйте интерфейсы в репозиториях, которые будут выполнять операции с базой данных через ORM или SQL-запросы. Это повысит гибкость кода и упростит тестирование и внедрение изменений.

  1. Прослойка для MessageBus
    Для работы с шиной сообщений (MessageBus) добавили промежуточный слой, который будет управлять отправкой и обработкой сообщений. Этот слой выполняет роль посредника между логикой приложения и фактическими вызовами MessageBus:

    • Управлять сериализацией и десериализацией сообщений.

    • Обрабатывать исключения и логгировать ошибки.

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

Прослойка также упрощает тестирование, поскольку можно использовать mock- или stub-реализации MessageBus для имитации поведения системы при тестировании бизнес-логики.

  1. Переход на EightPointsGuzzle
    Библиотека EightPointsGuzzle предоставила удобный интеграционный слой с Guzzle для Symfony, предлагая стандартные middleware для логгирования, обработки ошибок и отслеживания запросов. Использование этого пакета позволяет:

    • Снизить сложность работы с внешними API за счет единого клиента для HTTP-запросов.

    • Настроить общие middleware для всех запросов, такие как аутентификация, ретраи и таймауты.

    • Легко интегрировать с Symfony-сервисами и конфигурацией, что упрощает подключение и настройку HTTP-клиента.

Переход на EightPointsGuzzle позволяет унифицировать работу с внешними API, улучшить логгирование и повысить надежность запросов за счет встроенных механизмов обработки ошибок.

Эти подходы помогли стандартизировать работу с инфраструктурным слоем, улучшить качество кода и упростить поддержку проекта.

Работа с Domain

По мере изучения домена вместе с командой начали формироваться субдомены и выделяться ограниченные контексты (bounded contexts). Это позволило более точно определить границы ответственности для каждого модуля, улучшить структурирование бизнес-логики и упростить работу с отдельными компонентами системы. В результате каждый субдомен стал представлять отдельную часть предметной области, со своими правилами, сущностями и взаимодействиями, что облегчает как поддержку, так и развитие проекта.

Инъекция зависимостей

Внедрение инъекции зависимостей (DI) в нашем проекте значительно улучшило управление зависимостями и сделало код более гибким и тестируемым. Вот основные шаги и принципы, которые были внедрены:

  1. Доступ к сервисам через DI
    Вместо того чтобы получать сервисы напрямую из контейнера, мы используем инъекцию зависимостей для передачи необходимых сервисов в классы через конструкторы или методы. Это позволяет:

    • Ясно видеть, какие зависимости необходимы каждому компоненту.

    • Упрощать тестирование, так как можно легко подменять зависимости на mock-объекты.

  2. Доступ к параметрам через DI
    Параметры конфигурации теперь также передаются через инъекцию зависимостей. Это позволяет избежать жесткой привязки к контейнеру и делает классы более независимыми. В результате:

    • Компоненты становятся более модульными и переиспользуемыми.

    • Упрощается управление настройками и конфигурациями, так как все параметры можно передать при создании объекта.

  3. Запрет на использование контейнера
    Мы запретили использование контейнера в классах и методах. Это означает, что компоненты не могут напрямую обращаться к контейнеру для получения зависимостей. Такие меры позволили:

    • Исключить зависимость от контейнера, что облегчает тестирование и делает код более чистым.

    • Повысить уровень абстракции, поскольку классы не знают о том, как управляются их зависимости.

    • Упростить миграцию на другие реализации DI, если это станет необходимым в будущем.

Инъекция зависимостей позволяет создать более поддерживаемую и гибкую архитектуру, что в свою очередь способствует улучшению качества кода и повышению производительности команды разработки.

Безопасность

Для повышения уровня безопасности в нашем проекте были предприняты следующие шаги:

  1. Переход от параметров к окружению (env)
    Мы заменили использование параметров конфигурации на переменные окружения. Это позволяет хранить чувствительную информацию, такую как ключи API и пароли, вне кода и конфигурационных файлов, что значительно снижает риск их утечки.

  2. Удаление паролей из репозитория
    Все пароли и конфиденциальные данные были удалены из репозитория. Вместо этого мы используем переменные окружения для их хранения. Это обеспечит защиту информации и предотвратит случайное раскрытие данных при работе с системой контроля версий.

  3. Перенос SQL-запросов из кода в репозитории
    SQL-запросы были вынесены в отдельные файлы или репозитории. Это не только улучшает структуру кода, но и позволяет применять лучшие практики, такие как использование миграций для управления схемой базы данных и данных.

  4. Использование шифрования для конфиденциальной информации
    Внедрение шифрования для хранения и передачи конфиденциальной информации. Это позволяет защитить данные даже в случае их компрометации. Например, можно использовать библиотеки для шифрования паролей и хранения токенов.

  5. Регулярный аудит безопасности кода
    Мы начали проводить регулярные аудиты безопасности кода, используя статические анализаторы и инструменты для проверки уязвимостей. Это помогает выявлять потенциальные проблемы до того, как они станут серьезными угрозами.

  6. Аутентификация и авторизация
    Мы улучшили механизмы аутентификации и авторизации, внедрив двухфакторную аутентификацию (2FA) и обновив политику доступа к ресурсам. Это обеспечивает дополнительный уровень защиты и предотвращает несанкционированный доступ.

  7. Логирование и мониторинг
    Внедрение систем логирования и мониторинга для отслеживания подозрительной активности и потенциальных угроз безопасности. Это позволяет своевременно реагировать на инциденты и проводить расследования.

  8. Удаление секретов из логов

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

Эти меры значительно улучшили безопасность нашего проекта, снизили риски и защитили конфиденциальные данные.

Рефакторинг

В процессе оптимизации кода и повышения его качества были предприняты следующие шаги:

  1. Удаление нерабочих тестов
    Все нерабочие и устаревшие тесты были удалены, что позволило улучшить качество тестового покрытия и повысить уверенность в стабильности кода. Это также освободило время для написания новых тестов, охватывающих актуальные функциональности.

  2. Удаление лишних команд и консюмеров
    Были удалены неиспользуемые команды и консюмеры, которые больше не имели практического применения. Это улучшило читаемость кода и снизило его сложность, что облегчает дальнейшую разработку и сопровождение.

  3. Удаление мертвого кода и комментариев
    Весь мертвый код и устаревшие комментарии были удалены, что сделало код более чистым и понятным. Это способствует лучшему восприятию логики приложения и облегчает его поддержку новыми разработчиками.

  4. Рефакторинг и упрощение структуры кода
    Проведен рефакторинг существующего кода с целью упрощения его структуры и улучшения читаемости. Это включает в себя разбивку больших функций на более мелкие, переименование переменных и классов для большей ясности.

  5. Обновление Swagger документации
    Мы провели полное обновление документации Swagger для нашего API. Это включает в себя добавление новых эндпоинтов, обновление описаний и примеров, а также исправление ошибок в существующих документах. Обновленная документация обеспечивает более точное представление функциональности API и упрощает работу разработчиков, которые его используют.

  6. Настройка коллекций Postman
    Создали и настроили коллекции Postman для автоматизации тестирования API. Это позволяет команде легко выполнять запросы, тестировать новые функциональности и делиться ими с другими членами команды. Коллекции также включают тесты для проверки ответов API, что способствует более быстрому выявлению проблем и повышает качество кода.

  7. Переименование cron-задач
    Провели ревизию cron-задач и переименовали (Добавили `cron:` перед каждой командой) их для большей ясности и соответствия общим стандартам именования. Это упрощает понимание назначения каждой задачи и облегчает их сопровождение. Переименование задач также помогает избежать путаницы, особенно в среде с большим количеством запланированных операций.

Улучшения производительности

Для улучшения производительности сайта, который изначально не был предназначен для высокой нагрузки, были предприняты следующие меры оптимизации:

  1. Анализ текущего состояния производительности
    Мы провели анализ текущей производительности сайта с помощью инструментов мониторинга, что позволило выявить узкие места и области для оптимизации.

  2. Оптимизация загрузки ресурсов
    Были оптимизированы изображения, шрифты и другие статические ресурсы. Это включает сжатие изображений и использование современных форматов, таких как WebP, что уменьшает время загрузки страниц.

  3. Кэширование
    Внедрение кэширования на уровне сервера и клиента. Использование заголовков кэширования и кэширования статических ресурсов значительно уменьшает количество запросов к серверу и ускоряет загрузку страниц для пользователей.

  4. Минификация и объединение файлов
    CSS и JavaScript файлы были минифицированы и объединены для уменьшения числа HTTP-запросов и уменьшения общего размера загружаемых ресурсов.

  5. Использование CDN (Content Delivery Network)
    Внедрение CDN для доставки статического контента, что помогает снизить время отклика за счет использования ближайших к пользователю серверов.

  6. Оптимизация базы данных
    Проведена оптимизация запросов к базе данных, включая индексацию, удаление неиспользуемых данных и упрощение сложных запросов.

  7. Устранение дедлоков
    Мы проанализировали и оптимизировали операции с базой данных для предотвращения дедлоков. Это включает в себя оптимизацию порядка выполнения транзакций и использование тайм-аутов, что позволяет избежать взаимных блокировок и улучшить общую производительность системы.

  8. Улучшение кода
    Рефакторинг кода для устранения избыточности и повышения его эффективности. Это включает в себя удаление мертвого кода и оптимизацию алгоритмов, использующихся в приложении.

  9. Мониторинг производительности
    Настроены системы мониторинга для отслеживания производительности сайта в реальном времени. Это помогает оперативно выявлять и устранять проблемы с производительностью.

  10. Тестирование под нагрузкой
    Проведены тесты под нагрузкой для определения предельной производительности сайта и выявления точек, которые могут вызвать сбои при увеличении числа пользователей.

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

DB (MySQL)

Для оптимизации работы с базой данных и повышения её эффективности были выполнены следующие действия:

  1. Стандартизация названий полей и таблиц
    Мы привели названия полей и таблиц к единому стилю, используя camelCase для улучшения читабельности и последовательности. Это упрощает понимание структуры базы данных и помогает разработчикам быстрее ориентироваться в коде.

  2. Добавление внешних ключей

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

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

  2. Слияние миграций
    Объединили более 1000 миграций в одну, что позволило упростить процесс обновления схемы базы данных в будущем. Это снизило вероятность ошибок при выполнении старых миграций и сделало процесс поднятия приложения более управляемым.

  3. Замена статических таблиц на ENUM
    Мы заменили статические таблицы на ENUM, что улучшило производительность и упростило поддержку данных, особенно для полей с ограниченным числом значений. Это также уменьшает избыточность данных и повышает читаемость.

  4. Добавление индексов для частых запросов
    Добавили индексы на поля, которые часто используются в запросах, что значительно увеличило скорость выполнения операций чтения. После анализа запросов были удалены устаревшие индексы, которые больше не использовались, что снизило накладные расходы на поддержку базы данных.

  5. Оптимизация запросов
    Проведена оптимизация SQL-запросов, что позволило уменьшить время выполнения и нагрузку на базу данных. Это включает в себя переработку сложных запросов и использование JOIN вместо подзапросов там, где это возможно.

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

NoSQL (MongoDB)

В скорее мы поняли что MongoDB был выбрат на хайпе. И не использовался по назначению. Поэтому мы составили план по уменьшению зоопарка технологий.

Оптимизация схемы данных: Удалили часть таблиц. Изменили оставшиеся таблицы для улучшения структуры данных и уменьшения дублирования.

Анализ текущего использования: Провели оценку, какие функции MongoDB используются в проекте и как их можно реализовать с помощью других баз данных, которые уже используются.

Обновление кодовой базы: Обновили код приложения, чтобы убрать зависимости от MongoDB.


🏫 Infrastructure

  1. Включение VPN на внутренние ресурсы: Настройка VPN обеспечивает безопасный доступ к внутренним ресурсам компании для сотрудников. Это позволяет защитить данные и поддерживать конфиденциальность при работе с чувствительной информацией.

  2. Миграция сервисов в облако Amazon: Ведется процесс миграции существующих сервисов в облачную инфраструктуру Amazon Web Services (AWS). Это дает возможность использовать масштабируемые ресурсы, повышать доступность приложений и оптимизировать затраты на обслуживание серверов.

  3. Настройки репликации ключевых элементов: Установлены параметры репликации для критически важных данных и сервисов. Это гарантирует высокую доступность и отказоустойчивость, а также обеспечивает защиту от потери данных в случае сбоя.

  4. Создание staging-сред для внутренней разработки: Созданы стейджи (staging environments) для тестирования и разработки новых функций. Это позволяет разработчикам безопасно проверять изменения, прежде чем они будут развернуты в продуктивной среде, минимизируя риски и обеспечивая стабильность работы приложения.

  5. Настройка кластера для Redis: Развернут кластер Redis для повышения производительности и обеспечение отказоустойчивости кэширования данных. Это позволяет эффективно обрабатывать высокие нагрузки и обеспечивает быстрый доступ к часто запрашиваемой информации.

  6. Миграция логов в Elasticsearch: Логи системы были перенесены в Elasticsearch для централизованного хранения и анализа. Это обеспечивает удобный доступ к данным, позволяет легко искать и визуализировать информацию о производительности и проблемах в системе.

  7. Распределение логов и бизнес данных в 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/


Комментарии

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

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