Полная перепись редко нужна как первый шаг. Сначала нужно понять, что мешает продукту, можно ли исправить это в текущей системе и где проходит безопасная граница изменений.
Сначала проверьте, нужна ли миграция.
Смена языка сама по себе не является целью. Сформулируйте конкретную проблему: стоимость развития, ограничения по нагрузке, устойчивость фоновых задач или сложность поддержки отдельного модуля. Затем сравните стоимость исправления в 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, они помогут быстрее выбрать границу первой задачи.
- Что требуется улучшить и как команда поймёт, что стало лучше?
- Какие данные и клиенты нельзя нарушить?
- Кто сейчас поддерживает систему и какие сроки переключения допустимы?