Symfony JSON-RPC API Bundle: 3 года боевой эксплуатации и 5 версий

Автор популярного бандла для JSON-RPC API на Symfony рассказывает, как три года продакшена в финтехе и HRM изменили код: от 17 отклонений от спецификации до утечки приватных полей и проблем с CORS. Читайте, какие уроки стали фичами и почему версия 5.1 — это не просто цифра.

Symfony JSON-RPC API Bundle: 3 года боевой эксплуатации и 5 версий

В августе 2023 года на Хабре появилась статья о JSON-RPC API Bundle для Symfony. Тогда это был молодой проект версии 1.x, который автор позиционировал как удобный инструмент для построения API. Сегодня бандл дорос до версии 5.1 и работает в нескольких продакшенах — от финтех-инструментов до HRM-систем. Но путь от первой до пятой версии был усеян не только фичами, но и серьезными проблемами, которые вскрылись именно в боевой эксплуатации. В этой статье — честный отчет о том, что произошло с кодом за три года и какие уроки извлек автор.

Семнадцать отклонений от спецификации: цена боевой эксплуатации

Первое, что бросается в глаза в отчете — это семнадцать найденных отклонений от спецификации JSON-RPC 2.0. Казалось бы, спецификация простая и четкая, но на практике все оказалось сложнее. Например, автор обнаружил, что его бандл неправильно обрабатывал ошибки парсинга JSON: вместо того чтобы возвращать стандартный объект ошибки с кодом -32700, он возвращал что-то свое. В другом случае метод notify (уведомление без ответа) мог случайно вернуть ответ, если клиент ожидал его отсутствия.

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

Каждое отклонение было исправлено, но процесс занял время. Автор признается, что многие ошибки были следствием недостаточного тестирования на граничных случаях. Спецификация JSON-RPC 2.0 не такая уж сложная, но ее нюансы требуют внимания: например, поле id в запросе может быть строкой, числом или null, и каждый случай нужно обрабатывать по-разному.

Приватные поля, утекавшие в ответы: проблема безопасности

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

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

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

CORS-заголовок через запятую: неожиданный баг

Еще один интересный баг был связан с CORS-заголовками. Бандл добавлял заголовок Access-Control-Allow-Origin в ответы, но если клиент сам устанавливал этот заголовок, то в итоге в ответе оказывалось два значения, разделенных запятой. Браузеры интерпретируют такую запись некорректно, и в результате фронтенд не мог получить доступ к API.

Проблема возникла из-за того, что бандл не проверял, установлен ли уже заголовок. В боевой среде это проявилось, когда фронтенд-разработчики начали добавлять свои CORS-настройки. Решение было простым: проверять, существует ли заголовок, и только тогда добавлять его. Но этот случай показал, как важно учитывать взаимодействие с другими компонентами.

Предыстория и контекст: почему так много проблем

Стоит отметить, что JSON-RPC — это не REST, и его спецификация оставляет больше свободы в деталях. Многие разработчики, переходящие с REST, ожидают, что JSON-RPC будет проще, но на деле простота обманчива. Например, в JSON-RPC нет понятия HTTP-статусов — все ошибки передаются в теле ответа. Это требует особого внимания к обработке ошибок.

Автор бандла начал проект в 2021 году, когда ему потребовалось быстро создать API для внутреннего инструмента. Он выбрал JSON-RPC, потому что он показался ему более подходящим для RPC-стиля, чем REST. Однако по мере роста проекта и подключения новых клиентов стали проявляться слабые места. Каждый новый продакшен выявлял новые проблемы, и бандл эволюционировал.

Как это работает: что изменилось в версии 5.1

Версия 5.1 — это не просто набор исправлений. Автор переработал архитектуру бандла, чтобы избежать повторения проблем. Главное изменение — введение строгой типизации запросов и ответов. Теперь бандл использует PHP-атрибуты для описания методов API, что позволяет автоматически генерировать документацию и валидировать входящие данные.

Также была добавлена поддержка batch-запросов, когда клиент может отправить несколько вызовов в одном запросе. Это востребовано в высоконагруженных системах, где количество HTTP-запросов критично. Бандл теперь корректно обрабатывает частичные ошибки в batch-запросах, возвращая ответы для успешных вызовов и ошибки для неудачных.

Еще одно важное улучшение — обработка ошибок. Теперь бандл поддерживает кастомные исключения, которые можно маппить на коды ошибок JSON-RPC. Это позволяет разработчикам передавать клиентам детальную информацию об ошибках, не раскрывая внутренние детали.

Кого затронет: разработчики, бизнес, пользователи

Для разработчиков, использующих Symfony, этот бандл может стать удобным инструментом, но важно понимать, что JSON-RPC — это не серебряная пуля. Если ваше API предназначено для публичного доступа и требует гибкости, возможно, REST или GraphQL будут лучше. Но для внутренних сервисов, где важна скорость и простота, JSON-RPC может быть отличным выбором.

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

В России и СНГ JSON-RPC пока не так популярен, как REST, но в финтехе и HRM он находит применение. Например, некоторые банковские системы используют JSON-RPC для обмена данными между микросервисами. Поэтому опыт автора может быть полезен локальным разработчикам.

Что будет дальше: планы и прогнозы

Автор планирует продолжать развитие бандла, но теперь с акцентом на стабильность и производительность. В ближайших планах — поддержка асинхронных вызовов и улучшение интеграции с популярными инструментами Symfony, такими как Messenger и Notifier.

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

В долгосрочной перспективе автор видит бандл как часть экосистемы Symfony, которая позволяет строить сложные API без лишней боли. Но он предупреждает, что JSON-RPC — это нишевый протокол, и его применение должно быть осознанным.

Итог

Три года боевой эксплуатации превратили простой бандл в зрелый инструмент, который прошел проверку реальными сценариями. Семнадцать отклонений от спецификации, утечки приватных полей и CORS-баги — все это стало уроками, которые теперь заложены в код. Если вы используете JSON-RPC или только планируете перейти на него, этот опыт стоит изучить. А сам бандл — хороший пример того, как открытый код развивается благодаря обратной связи из продакшена.