gRPC против REST: начало работы с лучшим протоколом API

Опубликовано: 2022-07-22

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

Многие стандартные технологии удовлетворяют потребности межсервисных коммуникаций распределенных систем, таких как REST, SOAP, GraphQL или gRPC. В то время как REST является предпочтительным подходом, gRPC является достойным соперником, предлагая высокую производительность, типизированные контракты и отличные инструменты.

Обзор ОТДЫХА

Передача репрезентативного состояния (REST) ​​— это средство получения данных службы или управления ими. REST API обычно строится на протоколе HTTP, используя URI для выбора ресурса и команду HTTP (например, GET, PUT, POST) для выбора желаемой операции. Тело запроса и ответа содержит данные, относящиеся к операции, а их заголовки содержат метаданные. Для иллюстрации рассмотрим упрощенный пример получения товара через REST API.

Здесь мы запрашиваем ресурс продукта с идентификатором 11 и направляем API для ответа в формате JSON:

 GET /products/11 HTTP/1.1 Accept: application/json

Учитывая этот запрос, наш ответ (нерелевантные заголовки опущены) может выглядеть так:

 HTTP/1.1 200 OK Content-Type: application/json { id: 11, name: "Purple Bowtie", sku: "purbow", price: { amount: 100, currencyCode: "USD" } }

Хотя JSON может быть удобочитаемым для человека, он не оптимален при использовании между службами. Повторяющийся характер ссылок на имена свойств — даже в сжатом виде — может привести к раздуванию сообщений. Давайте посмотрим на альтернативу для решения этой проблемы.

Обзор gRPC

Удаленный вызов процедур gRPC (gRPC) — это основанный на контракте протокол межплатформенной связи с открытым исходным кодом, который упрощает взаимодействие между службами и управляет им, предоставляя набор функций внешним клиентам.

Построенный на основе HTTP/2, gRPC использует такие функции, как двунаправленная потоковая передача и встроенная безопасность транспортного уровня (TLS). gRPC обеспечивает более эффективную связь с помощью сериализованных двоичных полезных данных. Он использует буферы протоколов по умолчанию в качестве механизма сериализации структурированных данных, подобно тому, как REST использует JSON.

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

  • Язык определения контрактов, найденный в файлах .proto (мы будем следовать proto3, последней спецификации языка буферов протоколов).
  • Сгенерированный код функции доступа
  • Библиотеки времени выполнения для конкретных языков

Удаленные функции, доступные в службе (определенные в файле .proto ), перечислены внутри узла службы в файле буфера протокола. Как разработчики, мы можем определять эти функции и их параметры, используя богатую систему типов протокольных буферов. Эта система поддерживает различные числовые типы и типы дат, списки, словари и значения NULL для определения наших входных и выходных сообщений.

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

Этот пример файла .proto определяет функцию для возврата записи о продукте с заданным идентификатором:

 syntax = "proto3"; package product; service ProductCatalog { rpc GetProductDetails (ProductDetailsRequest) returns (ProductDetailsReply); } message ProductDetailsRequest { int32 id = 1; } message ProductDetailsReply { int32 id = 1; string name = 2; string sku = 3; Price price = 4; } message Price { float amount = 1; string currencyCode = 2; }
Фрагмент 1: определение сервиса ProductCatalog

Строгая типизация и порядок полей в proto3 делают десериализацию сообщений значительно менее трудоемкой, чем парсинг JSON.

Сравнение REST и gRPC

Напомним, что наиболее важными моментами при сравнении REST и gRPC являются:

ОТДЫХАТЬ gRPC
Кроссплатформенность Да Да
Формат сообщения Пользовательский, но обычно JSON или XML Буферы протоколов
Размер полезной нагрузки сообщения Средний/большой Маленький
Сложность обработки Высшее (разбор текста) Нижний (четко определенная бинарная структура)
Поддержка браузера Да (родной) Да (через gRPC-Web)

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

Реализация службы gRPC

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

Создание проекта API

Для начала мы создадим проект .NET 6 в Visual Studio 2022 Community Edition (VS). Мы выберем шаблон ASP.NET Core gRPC Service и назовем как проект (мы будем использовать InventoryAPI ) , так и наше первое решение в нем ( Inventory ) .

Диалоговое окно «Настройка нового проекта» в Visual Studio 2022. На этом экране мы ввели «InventoryAPI» в поле «Имя проекта», мы выбрали «C:\MyInventoryService» в поле «Расположение» и ввели «Inventory» в поле «Имя решения». . Мы оставили флажок «Поместить решение и проект в один каталог».

Теперь давайте выберем. NET 6.0 (долгосрочная поддержка) для нашего фреймворка:

Диалоговое окно «Дополнительная информация» в Visual Studio 2022. На этом экране мы выбрали «.NET 6.0 (долгосрочная поддержка)» в раскрывающемся списке Framework. Мы оставили «Включить Docker» неотмеченным.

Определение нашего продукта-услуги

Теперь, когда мы создали проект, VS отображает образец службы определения прототипа gRPC с именем Greeter . Мы переделаем основные файлы Greeter в соответствии с нашими потребностями.

  • Чтобы создать наш контракт, мы заменим содержимое greet.proto фрагментом 1, переименовав файл product.proto .
  • Чтобы создать наш сервис, мы заменим содержимое файла GreeterService.cs фрагментом 2, переименовав файл ProductCatalogService.cs .
 using Grpc.Core; using Product; namespace InventoryAPI.Services { public class ProductCatalogService : ProductCatalog.ProductCatalogBase { public override Task<ProductDetailsReply> GetProductDetails( ProductDetailsRequest request, ServerCallContext context) { return Task.FromResult(new ProductDetailsReply { Id = request.Id, Name = "Purple Bowtie", Sku = "purbow", Price = new Price { Amount = 100, CurrencyCode = "USD" } }); } } }
Фрагмент 2: ProductCatalogService

Теперь служба возвращает жестко закодированный продукт. Чтобы служба заработала, нам нужно всего лишь изменить регистрацию службы в Program.cs , чтобы она ссылалась на новое имя службы. В нашем случае мы переименуем app.MapGrpcService<GreeterService>(); в app.MapGrpcService<ProductCatalogService>(); чтобы сделать наш новый API работоспособным.

Справедливое предупреждение: не ваш стандартный тест протокола

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

Создание клиента

Чтобы протестировать нашу службу, давайте воспользуемся базовым шаблоном консольного приложения VS и создадим клиент gRPC для вызова API. Я назвал свой InventoryApp .

Для удобства давайте сошлемся на относительный путь к файлу, по которому мы будем делиться нашим контрактом. Мы добавим ссылку вручную в файл .csproj . Затем мы обновим путь и установим режим Client . Примечание. Я рекомендую вам ознакомиться со структурой локальных папок и быть уверенным в ней, прежде чем использовать относительные ссылки.

Вот ссылки .proto в том виде, в каком они появляются в файлах сервисного и клиентского проектов:

Файл проекта службы
(Код для копирования в файл проекта клиента)
Файл клиентского проекта
(После вставки и редактирования)
 <ItemGroup> <Content Update="Protos\product.proto" GrpcServices="Server" /> </ItemGroup>
 <ItemGroup> <Protobuf Include="..\InventoryAPI\Protos\product.proto" GrpcServices="Client" /> </ItemGroup>

Теперь, чтобы вызвать нашу службу, мы заменим содержимое Program.cs . Наш код выполнит ряд задач:

  1. Создайте канал, представляющий расположение конечной точки службы (порт может отличаться, поэтому фактическое значение см. в файле launchsettings.json ).
  2. Создайте клиентский объект.
  3. Составьте простой запрос.
  4. Отправьте запрос.
 using System.Text.Json; using Grpc.Net.Client; using Product; var channel = GrpcChannel.ForAddress("https://localhost:7200"); var client = new ProductCatalog.ProductCatalogClient(channel); var request = new ProductDetailsRequest { Id = 1 }; var response = await client.GetProductDetailsAsync(request); Console.WriteLine(JsonSerializer.Serialize(response, new JsonSerializerOptions { WriteIndented = true })); Console.ReadKey();
Фрагмент 3: Новая Program.cs

Подготовка к запуску

Чтобы протестировать наш код, в VS мы щелкнем правой кнопкой мыши решение и выберем Set Startup Projects . В диалоговом окне «Страницы свойств решения» мы:

  • Выберите переключатель рядом с несколькими запускаемыми проектами и в раскрывающемся меню «Действие» установите для обоих проектов ( InventoryAPI и InventoryApp ) значение Start .
  • Нажмите ОК .

Теперь мы можем запустить решение, нажав Start на панели инструментов VS (или нажав клавишу F5 ). Появятся два новых окна консоли: одно, чтобы сообщить нам, что служба прослушивает, другое, чтобы показать нам детали полученного продукта.

Совместное использование контрактов gRPC

Теперь воспользуемся другим методом для подключения клиента gRPC к определению нашего сервиса. Наиболее доступное клиенту решение для совместного использования контрактов — сделать наши определения доступными через URL-адрес. Другие варианты либо очень ненадежны (общий доступ к файлу осуществляется через путь), либо требуют больше усилий (контракт, передаваемый через собственный пакет). Совместное использование через URL-адрес (как это делают SOAP и Swagger/OpenAPI) является гибким и требует меньше кода.

Для начала сделайте файл .proto доступным как статическое содержимое. Мы обновим наш код вручную, потому что пользовательский интерфейс в действии сборки установлен на «Protobuf Compiler». Это изменение указывает компилятору скопировать файл .proto , чтобы его можно было обслуживать с веб-адреса. Если бы этот параметр был изменен через пользовательский интерфейс VS, сборка прервалась бы. Итак, наш первый шаг — добавить фрагмент 4 в файл InventoryAPI.csproj :

 <ItemGroup> <Content Update="Protos\product.proto"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <Content Include="Protos\product.proto" CopyToPublishDirectory="PreserveNewest" /> </ItemGroup>
Фрагмент 4: код для добавления в файл проекта службы InventoryAPI

Затем мы вставляем код во фрагмент 5 в верхней части файла ProductCatalogService.cs , чтобы настроить конечную точку для возврата нашего файла .proto :

 using System.Net.Mime; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders;
Фрагмент 5: Импорт Namespace

А теперь мы добавляем фрагмент 6 непосредственно перед app.Run() , также в файле ProductCatalogService.cs :

 var provider = new FileExtensionContentTypeProvider(); provider.Mappings.Clear(); provider.Mappings[".proto"] = MediaTypeNames.Text.Plain; app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "Protos")), RequestPath = "/proto", ContentTypeProvider = provider }); app.UseRouting();
Фрагмент 6: Код для обеспечения доступа к файлам .proto через API

После добавления фрагментов 4–6 содержимое файла .proto должно быть видно в браузере.

Новый тестовый клиент

Теперь мы хотим создать новый консольный клиент, который мы будем подключать к нашему существующему серверу с помощью мастера зависимостей VS. Проблема в том, что этот мастер не говорит по HTTP/2. Поэтому нам нужно настроить наш сервер для обмена данными по HTTP/1 и запустить сервер. Теперь, когда наш сервер предоставляет доступ к файлу .proto , мы можем создать новый тестовый клиент, который подключается к нашему серверу с помощью мастера gRPC.

  1. Чтобы изменить наш сервер для общения по HTTP/1, мы отредактируем наш JSON-файл appsettings.json :
    1. Настройте поле Protocol (находится по пути Kestrel.EndpointDefaults.Protocols ) для чтения Https .
    2. Сохраните файл.
  2. Чтобы наш новый клиент мог прочитать эту информацию о proto , сервер должен быть запущен. Первоначально мы запускали и предыдущий клиент, и наш сервер из диалогового окна Set Startup Projects VS. Настройте серверное решение так, чтобы запускался только серверный проект, а затем запустите решение. (Теперь, когда мы изменили версию HTTP, наш старый клиент больше не может взаимодействовать с сервером.)
  3. Затем создайте новый тестовый клиент. Запустите другой экземпляр VS. Мы повторим шаги, описанные в разделе « Создание проекта API» , но на этот раз выберем шаблон консольного приложения . Мы назовем наш проект и решение InventoryAppConnected .
  4. Создав клиентское шасси, мы подключимся к нашему серверу gRPC. Разверните новый проект в обозревателе решений VS.
    1. Щелкните правой кнопкой мыши Зависимости и в контекстном меню выберите Управление подключенными службами .
    2. На вкладке Подключенные службы щелкните Добавить ссылку на службу и выберите gRPC .
    3. В диалоговом окне «Добавить ссылку на службу» выберите параметр « URL » и введите http -версию адреса службы (не забудьте взять случайно сгенерированный номер порта из launchsettings.json ) .
    4. Щелкните Готово , чтобы добавить ссылку на службу, которую можно легко поддерживать.

Не стесняйтесь проверять свою работу по образцу кода для этого примера. Поскольку под капотом VS сгенерировал того же клиента, который мы использовали в нашем первом цикле тестирования, мы можем дословно повторно использовать содержимое файла Program.cs из предыдущей службы.

Когда мы меняем контракт, нам нужно изменить определение gRPC нашего клиента, чтобы оно соответствовало обновленному определению .proto . Для этого нам нужно только получить доступ к подключенным службам VS и обновить соответствующую запись службы. Теперь наш проект gRPC завершен, и теперь легко синхронизировать нашу службу и клиент.

Ваш следующий проект-кандидат: gRPC

Наша реализация gRPC дает представление о преимуществах использования gRPC из первых рук. У REST и gRPC есть свои идеальные варианты использования в зависимости от типа контракта. Однако, когда подходят оба варианта, я рекомендую вам попробовать gRPC — это позволит вам опережать конкурентов в будущем API.