Как не копировать-вставлять свой файл Proto

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

Существует множество протоколов и форматов связи, от широко известных (SOAP, JSON, SSH) до нишевых (STOMP, SPDY, DICT). Одним из них является gRPC, который в основном используется для связи между внутренними серверами (метко названный Удаленный вызов процедуры или сокращенно RPC). В этой статье мы расскажем вам о том, как мы используем gRPC в Моладине, и какие подходы мы используем для централизации его API между микросервисами.

Краткое руководство по gRPC

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

Внутри gRPC использует HTTP/2 в качестве транспортного протокола и Protobuf в качестве формата обмена сообщениями. Сердцем Protobuf (и, соответственно, gRPC) являются файлы Proto: файлы, которые описывают, как две системы будут взаимодействовать и какой формат данных. Этот файл Proto можно рассматривать как контракт между взаимодействующими системами.

syntax = "proto3";
service Greeter {
    rpc Greet (GreetingRequest) returns (string);
}
message GreetingRequest {
    string name = 1;
}

Приведенный выше пример Proto означает, что существует удаленный метод с именем Greet, который принимает объект GreetingRequest с текстом name внутри (скажем, name равен "world" ), и метод возвращает текст (например, "Hello world"). Вы можете проверить Документацию Protobuf для получения полных возможностей файла Proto.

Чтобы использовать файл Proto в коде, его можно скомпилировать на поддерживаемый язык программирования по вашему выбору с помощью protoc. Затем этот сгенерированный код можно импортировать и использовать в вашем коде для отправки/получения сообщений gRPC.

Единый источник достоверной информации об API

Для того, чтобы произошла коммуникация, необходимы как минимум две стороны. То же самое относится и к RPC, будет как минимум 2 системы, которым потребуется контракт Proto. В связи с этим возникает вопрос: как можно синхронизировать контракт gRPC с этими системами?

Один из способов добиться более синхронизированного распространения — централизовать файлы контрактов, чтобы был только один источник правды. Поскольку файлы Proto основаны на тексте, система контроля версий (например, Git) является хорошим способом их хранения. Таким образом, изменения контракта могут быть просто git pulled.

Хотя идея хороша и все такое, у централизации контрактов Proto есть свои проблемы, которые необходимо решить:

  • Когда есть несколько сервисов с собственными контрактами (таким образом, с разными файлами Proto), означает ли это, что существует несколько репозиториев API?
  • Мы храним только файлы Proto в VCS? Или сгенерированный код также должен быть включен?
  • Кто отвечает за создание кода из файлов Proto и развертывание артефакта?

Подходы к централизации контрактов

Чтобы решить вышеупомянутые проблемы, мы можем выбрать несколько вариантов, каждый из которых имеет свои особенности.

Подход 1: монорепозиторий для хранения кода всех сервисов и прототипов

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

Эта структура обладает всеми преимуществами монорепозитория и дает понять, какой сервис владеет файлами Proto. Другие сервисы также могут напрямую использовать сгенерированный код Proto, просто import /require /include-ing, поскольку весь код находится в одном репозитории.

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

Подход 2: файлы Proto в одном репозитории

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

Этот подход помещает все контракты Proto в один репозиторий, что делает управление версиями файлов Proto более понятным, чем предыдущий подход.

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

Подход 3: файлы Proto и сгенерированный код в одном репозитории

Этот подход похож на предыдущий. Но вместо того, чтобы хранить только файлы Proto, репозиторий API также хранит сгенерированный код Proto. Службе, для которой требуется код Proto, нужно только import /require /include их.

Этот подход очень подходит для языков программирования, которые поддерживают импорт библиотек с помощью ссылок Git, таких как JavaScript, Python и Go. Сгенерированный код Proto может использоваться непосредственно кодом микросервиса.

Подход 4: каждый репозиторий службы хранит свои собственные файлы Proto

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

Несмотря на то, что файлы Proto разбросаны по разным репозиториям, контракты все еще несколько «централизованы», потому что все API, связанные с microeservice_a, имеют только один источник — файлы Proto внутри microeservice_a репозитория.

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

Выбранный

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

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

  • Moladin использует JavaScript, TypeScript и Go в качестве основных языков программирования, каждый из которых может импортировать библиотеку с помощью ссылок Git.
  • Процесс CI/CD можно использовать для запуска protoc выполнения при наличии изменений в файлах Proto.
  • Простота использования для разработчиков, поскольку им не нужно компилировать файлы Proto вручную, поэтому на их рабочую станцию ​​​​устанавливается меньше программного обеспечения.

Это все на данный момент. Надеюсь, вам понравится эта история о техническом путешествии Моладина, и мы скоро увидимся снова!

По всем вопросам вы можете связаться с нами по адресу [email protected].
#MoApapunMoladin