Пару лет назад мы достаточно спокойно работали нашей небольшой командой и делали хостинг. Вышло так, что каждый сервис в системе обладал собственным уникальным и неповторимым API. Но потом это стало проблемой и было решено все переделать. Мы расскажем о том, как объединить внешнее API с внутренним и что делать, если у вас много кода на PHP, но хочется воспользоваться преимуществами gRPC. Сейчас очень много говорят про микросервисы и SOA в целом. Наша инфраструктура не исключение: ведь мы занимаемся хостингом и наши сервисы позволяют управлять почти тысячей серверов. Со временем сервисов в нашей системе стало появляться все больше: стали регистратором доменов — выносим регистрацию в отдельный сервис; метрик с серверов стало очень много — пишем сервис, который делает выборки из ClickHouse / InfluxDB; Нужно сделать эмулятор запуска задач «как через Crontab»; для пользователей — пишем сервис. Наверное, это многим будет знакомо. Входящих задач в разработке много. Количество различных сервисов растет плавно и, вроде бы, незаметно. Заранее учесть все будущие нюансы невозможно, поэтому на смену одним API приходили другие, более лучшие. Но настал день, когда стало очевидно, что у нас развелось слишком много протоколов:  Чем больше становилось сервисов и связей между ними, тем дольше и сложнее было решать задачи: требовалось изучить несколько разных API, написать к ним клиенты и только потом приступить к реальной работе. Ах, да… еще ведь документация нужна. Иначе в чатиках происходят такие диалоги:
 — Ребят, как мне получить баланс пользователя из биллинга?
 — Сделай вызов в billing/getBalance(customerId)
 — А список услуг как получить?
 — Не помню, поищи нужный контроллер в 
 Короче говоря, зародилась мечта о волшебном едином стандарте и технологии для создания сетевых API, которые решат все проблемы и сэкономят нам вагон времени.  Немного подумав, мы составили свой небольшой список требований: В результате поисков, оценки и небольших тестов мы остановились на gRPC. Этот фреймворк не новый и о нем уже писали на Хабре. Из коробки он удовлетворял почти всем нашим требованиям. Если в двух словах: Однако, идеальных технологий не существует. Для нас возникло несколько камней преткновения: К счастью, мы достаточно легко решили эти проблемы. Далее я буду предполагать, что читатель знаком с gRPC. Если нет — лучше сначала обратиться к упомянутой выше статье. Да, действительно. OpenAPI и набор инструментов, предоставляемые Swagger'ом, выглядят заманчиво. Сразу надо сказать, что сравнивать OpenAPI и gRPC не совсем корректно. gRPC — это прежде всего фреймворк, решающий техническую проблему RPC-взаимодействия. Он предлагает свой протокол, способ сериализации, язык описания сервисов и некоторый тулинг. OpenAPI же в первую очередь является спецификацией, пытающаяся стать единым стандартом для описания интерфейсов. Пускай сейчас она сильно ориентирована в сторону REST, но есть предложения добавить поддержку RPC. Возможно, в будущем OpenAPI станет современным аналогом веб-служб и WSDL. Тем не менее, у нашей команды нашлось несколько своих аргументов в пользу ипользования gRPC вместо OpenAPI: Если подвести небольшой итог, то можно сказать, что для нас OpenAPI оказался слишком объемен и не всегда предлагал адекватные решения. Напомню, что наша конечная цель — перейти на единый технический и организационный стандарт для RPC. Внедрение принципов REST потребовало бы переосмысления и рефакторинга многих вещей, что было бы уже задачей из совсем другой весовой категории. Также можно ознакомиться, например, с этой статьей-сравнением. В целом мы согласны с ней. Как я говорил выше, у нас много бизнес-логики написано на PHP. Если обратиться к документации, то нас ожидает облом: из-за особенностей модели выполнения кода он не может выступать в роли сервера (разнообразные reactphp не в счет). Зато он неплохо работает в роли клиента и, если скормить генератору кода proto-файл с описанием сервиса, он честно сгенерирует классы для всех структур (request и response). А значит, проблема вполне решаема. Все что мы нашли на тему работы PHP в роли сервера — обсуждение на эту тему в Google Groups. В этом обсуждении один из участников сообщил, что они работают над возможностью проксирования gRPC в FastCGI (его хочет видеть используемый нами PHP-FPM). Это именно то, что мы искали. К сожалению, связаться, узнать статус этого проекта и поучаствовать в нем нам не удалось. В связи с этим было принято решение написать свою небольшую прокси, которая могла бы принимать запросы и конвертировать их в FactCGI. Так как gRPC работает поверх HTTP/2 и вызов метода в нем по-факту является обычным HTTP-запросом, задача не является сложной. В результате мы достаточно быстро сделали такую прокси на языке Go. Для своей работы она требует лишь небольшой конфиг с информацией о том, куда проксировать. Ее код мы опубликовали для всех желающих. Схема работы следующая:  Таким образом принцип обработки запроса в PHP очень простой: При запросе getSomeInfo в параметре r будет содержаться api.customer.customer-service/get-some-info. Концентрированный пример обработки запроса в приложении: Как можно заметить, все достаточно просто. Вы можете реализовать эту логику в любом популярном фреймворке и сделать удобный слой контроллеров, в которых вся сериализация/десериализация будет происходить автоматически. Обработка исключений и конвертация их в соответствующие gRPC statuscodes реализована на уровене приложения и происходит автоматически.
Все, что требуется разработчику в итоге — это создать Action и в сигнатуре метода run указать ожидаемые типы для запроса и ответа. Для генерации кода в PHP Преимущества Недостатки Если вопросы с внутренним мессаджингом мы более-менее решили, то еще остался фронтенд. В идеале, хочется стремиться к использованию единого стека технологий и для бекенда и для фронтенда: это проще и дешевле. Поэтому мы начали изучать этот вопрос. Почти сразу нашли проект grpc-gateway, позволяющий генерировать прокси для конвертации gRPC/Protobuf в HTTP/JSON. Кажется, что это неплохое решение для отдачи API на фронтенд и тем клиетам, которые не хотят или не могут использовать gRPC (например, если нужно по-быстрому написать какой-нибудь одноразовый скрипт на bash'е). Об этом проекте также есть статья на Хабре, поэтому буквально в двух словах: плагин к protoc на основе переданных .proto-файлов с описанием сервиса и специальной мета-информации об HTTP-роутах в них генериурет код для реверс-прокси. Далее руками пишется main-файл, в котором просто запускается сгеренрированный прокси-сервер (авторы grpc-gateway подробно описывают все действия в README.md). Правда, из коробки для нас нашлась пара неудобств: Короче говоря, требуется вносить изменения часто и быстро. Придется немного поработать над удобством. Сам grpc-gateway является плагином к protoc, который генерирует прокси и написан на Go. Исходя из этого решение напрашивается само-собой: написать генератор, который генерирует плагин, который генерирует прокси =) Ну и автоматизировать запуск и деплой всего этого в нашем Gitlab CI.  В результате получился генератор генератора, принимающий на вход простой конфиг: После его изменения и старта сборки, в нашем CI запускается генератор, скачивающий все нужные репозитории на основе конфига, генерирует код для main-файла, оборачивает саму прокси различными middleware и на выходе получает готовый бинарник, который затем деплоится на продакшн. Преимущества Недостатки В результате всех этих технических пертурбаций мы смогли довольно быстро подготовить нашу инфраструктуру к переходу на единый протокол мессаджинга. Таким образом, мы смогли упростить и ускорить обмен информацией между разработчиками, добавили нашим интерфейсам строгости в вопросе типов, перешли к принципу проектирования Design First и предварительному Code Review на уровне межсервисных интерфейсов. Получилось как-то так:
 На текущий момент мы перевели большую часть внутреннего мессаджинга на gRPC и работаем над новым публичным API. Все это происходит в фоновом режиме по мере возможностей. Однако, этот процесс не настолько сложен, как казалось ранее. Взамен мы получили возможность быстро и единообразно проектировать наши API, обмениваться ими между разработчиками, проводить Code Review и генерировать клиенты. Для случаев, когда очень нужен HTTP (например, для внутренних веб-интерфейсов), мы просто добавляем несколько аннотаций, дописываем пару строк в конфиг grpc-gateway и получаем готовый endpoint. Еще нам хотелось бы, что бы в будущем стало возможным использовать gRPC или аналогичный протокол прямо на фронтенде =) Нельзя сказать, что обошлось без косяков и сложностей. Среди интересных проблем, характерных для gRPC, мы выделили следующие: Помимо технических моментов, описанных в этой статье, были и другие вещи, которым нужно уделить пристальное внимание при переходе на новое API: Надеемся, что наш опыт будет полезен командам, работающими над долгоживущими проектами и готовыми бороться с тем, что обычно называют термином «исторически сложилось».