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

Сначала проверьте, нужна ли миграция.

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

01 / ВАРИАНТ

Оставить PHP

Если узкое место локально и устраняется профилированием, SQL, кэшем или изменением архитектуры — начните с этого.

02 / ВАРИАНТ

Выделить Go-сервис

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

03 / ВАРИАНТ

Переносить шире

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

Хороший первый вопрос

Какой измеримый результат нужен бизнесу и может ли его дать изменение без переноса?

Составьте карту работающей системы.

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

  • Запишите текущие ответы API: статусы, формат ошибок, пагинацию, сортировку, авторизацию.
  • Определите владельца данных и сценарии записи: кто меняет записи, когда и с какими побочными эффектами.
  • Соберите исходные показатели по важным сценариям: задержки, ошибки, нагрузка и операционные затраты.

Результат этого этапа

Граница первого переноса, список обязательных контрактов и критерии, по которым будет проверяться результат.

Практический шаблон

Матрица для выбора первого участка

Шаблон для обсуждения, а не данные клиентского проекта. Заполните его для одной функции до оценки переноса.

Проблема и исходная точка

Какой сценарий ограничен? Запишите текущее поведение, задержку и ошибки на типичной нагрузке.

Граница и данные

Опишите входы и выходы функции, зависимости и владельца записей. Отметьте, что остаётся в PHP.

Совместимость и возврат

Какие ответы API обязаны совпасть? Как включить новый путь и вернуться к прежнему при ошибке?

Переносите по проверяемой границе.

Самый безопасный объём — тот, который можно включить, наблюдать и при необходимости отключить отдельно. Конкретная схема зависит от архитектуры: иногда подходит новый сервис рядом с PHP, иногда постепенная замена одного потока внутри существующего продукта.

Такой подход называют Strangler Fig: новая реализация постепенно забирает сценарии у старой, пока та не перестанет быть нужна. Перед переключением помогает теневой трафик: копия реальных запросов идёт в новый путь, его ответы сравниваются с ответами PHP, а клиент по-прежнему получает ответ старой системы.

01 /

Выберите сценарий

Начните с функции с понятными входами, выходами и владельцем данных.

02 /

Зафиксируйте совместимость

Покройте критичные API-пути контрактными и интеграционными проверками.

03 /

Определите стратегию данных

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

04 /

Включайте постепенно

Сравнивайте поведение и показатели на ограниченном трафике, предусмотрите переключатель и откат.

Для финансовых значений, дат, локалей и порядка обработки событий отдельно проверьте совпадение поведения. Формально одинаковый JSON не означает одинаковый результат для клиента.

Где ломается совместимость ответов.

Контрактные тесты ловят изменения схемы, но PHP и Go по-разному сериализуют одни и те же данные. Эти различия стоит проверить до первого переключения:

  • Пустые массивы. В PHP json_encode([]) даёт [] и для списка, и для пустого ассоциативного массива, где клиент ждёт {}. В Go nil-срез превращается в null, пустая map — в {}.
  • Пропавшие поля. Тег omitempty в Go убирает из ответа поля со значениями 0, "" и false. В ответе PHP эти поля были, и клиент может считать их обязательными.
  • Порядок ключей. PHP сохраняет порядок добавления ключей, а Go при сериализации map сортирует ключи по алфавиту. Обычно это неважно, но ломает сравнение ответов как строк и клиентов, которые полагаются на порядок.
  • Числа. В PHP целое при переполнении молча становится float, а точность float в JSON зависит от serialize_precision. Денежные суммы надёжнее передавать строкой или в минимальных единицах.
  • Даты. PHP берёт таймзону из date.timezone, Go — из time.Local или явно заданной зоны. Сверьте зону и формат (DATE_ATOM и time.RFC3339) на реальных ответах.
  • Авторизация. Сессии PHP в файлах или в Redis хранятся в формате PHP, и Go-сервис не прочитает их напрямую. Заранее решите, как Go проверяет пользователя: общий токен (JWT), отдельная проверка через PHP или шлюз перед обоими сервисами.

Надёжный способ найти расхождения — прогнать одни и те же запросы через оба пути и сравнить ответы как структуры данных, а не как строки.

Что часто упускают при оценке.

  • Двойная запись без ясного владельца данных и правил разрешения конфликтов.
  • Скрытые зависимости: cron, очереди, ручные операции и внутренние потребители API.
  • Изменения поведения на краях: null, таймзоны, десятичные числа, порядок и повторные запросы.
  • Запуск без наблюдения и сценария отката, когда ошибки видны только пользователям.

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

Что прислать для первого разговора.

Не нужен готовый технический план. Достаточно описать бизнес-цель, текущую роль PHP-сервиса, предполагаемое узкое место и ограничения запуска. Если есть графики задержек, схема интеграций или описание API, они помогут быстрее выбрать границу первой задачи.

  • Что требуется улучшить и как команда поймёт, что стало лучше?
  • Какие данные и клиенты нельзя нарушить?
  • Кто сейчас поддерживает систему и какие сроки переключения допустимы?