Создание простой службы gRPC на стороне клиента

Создание простой службы gRPC на стороне клиента

16 марта 2023 г.

Большинство компонентов межсистемной связи, использующих REST, сериализуют свою полезную нагрузку в JSON. На данный момент в JSON отсутствует широко используемый стандарт проверки схемы: схема JSON не получила широкого распространения. Стандартная проверка схемы позволяет делегировать проверку сторонней библиотеке и выполнять ее. Без него мы должны вернуться к ручной проверке кода. Хуже того, мы должны синхронизировать код проверки со схемой.

XML имеет готовую проверку схемы: XML-документ может объявлять грамматику, которой он должен соответствовать. SOAP, основанный на XML, также выигрывает от этого.

Другие варианты сериализации имеют возможность проверки схемы: например,, Avro, Kryo и буферы протокола. Интересно, что gRPC использует Protobuf для предоставления RPC через распределенные компоненты:

<цитата>

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

-- Почему gRPC?

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

Простой сервис gRPC

Документация gRPC является исчерпывающей, поэтому вот краткое изложение:

* gRPC — это инфраструктура удаленного вызова процедур. * Он работает с широким спектром языков * Он зависит от буферов протокола:

<цитата>

Протокольные буферы – это независимый от языка и платформы расширяемый механизм Google для сериализации структурированных данных. Представьте себе XML, но меньше, быстрее и проще. Вы один раз определяете, как должны быть структурированы ваши данные, а затем можете использовать специально сгенерированный исходный код, чтобы легко записывать и считывать структурированные данные из различных потоков данных и с использованием различных языков.

-- Буферы протокола

* Это часть портфолио CNCF и в настоящее время находится на стадии инкубации

Давайте настроим нашу службу gRPC. Мы будем использовать Java, Kotlin, Spring Boot и специальный проект интеграции gRPC Spring Boot. Структура проекта содержит два проекта: один для модели и один для кода. Начнем с типового проекта.

Я не хотел чего-то сложного; достаточно повторного использования простого примера: запрос отправляет строку, а ответ содержит префикс Hello. Мы разрабатываем эту модель в специальном файле схемы Protobuf:

syntax = "proto3";                                        //1

package ch.frankel.blog.grpc.model;                       //2

option java_multiple_files = true;                        //3
option java_package = "ch.frankel.blog.grpc.model";       //3
option java_outer_classname = "HelloProtos";              //3

service HelloService {                                    //4
    rpc SayHello (HelloRequest) returns (HelloResponse) {
    }
}

message HelloRequest {                                    //5
    string name = 1;                                      //6
}

message HelloResponse {                                   //7
    string message = 1;                                   //6
}

  1. Версия определения Protobuf
  2. Пакет
  3. Конфигурация для Java
  4. Определение службы
  5. Определение запроса
  6. Определение поля. Сначала идет тип, затем название и, наконец, порядок.
  7. Определение ответа

Мы будем использовать Maven для генерации шаблонного кода Java:

<project>
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-stub</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-protobuf</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>jakarta.annotation</groupId>              <!--1-->
      <artifactId>jakarta.annotation-api</artifactId>
      <version>1.3.5</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>                 <!--2-->
        <artifactId>os-maven-plugin</artifactId>
        <version>1.7.1</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>    <!--3-->
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>${protobuf-plugin.version}</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
  1. Зависимости во время компиляции
  2. Поиск информации об операционной системе. Используется в следующем плагине
  3. Создать код Java из файла proto

После компиляции структура должна выглядеть примерно так:

Мы можем упаковать классы в JAR и использовать его в проекте веб-приложения. Последний есть в Kotlin, но только потому, что это мой любимый язык JVM.

Нам нужна только конкретная стартовая зависимость Spring Boot для интеграции конечных точек gRPC с Spring Boot:

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-server-spring-boot-starter</artifactId>
  <version>2.14.0.RELEASE</version>
</dependency>

Вот важный бит:

@GrpcService                                                        //1
class HelloService : HelloServiceImplBase() {                       //2
  override fun sayHello(
      request: HelloRequest,                                        //2
      observer: StreamObserver<HelloResponse>                       //3
  ) {
    with(observer) {
      val reply = HelloResponse.newBuilder()                        //2
                               .setMessage("Hello ${request.name}") //4
                               .build()
      onNext(reply)                                                 //5
      onCompleted()                                                 //5
    }
  }
}
  1. Программа grpc-server-spring-boot-starter обнаруживает аннотацию и творит чудеса
  2. Эталонные классы, созданные в указанном выше проекте
  3. Сигнатура метода допускает параметр StreamObserver. Этот класс взят из grpc-stub.jar
  4. .
  5. Получите запрос и добавьте к нему префикс, чтобы создать ответное сообщение.
  6. Воспроизвести события

Теперь мы можем запустить веб-приложение с помощью ./mvnw spring-boot:run.

Тестирование службы gRPC

Смысл сообщения в том, что доступ к службе gRPC с помощью обычных инструментов невозможен. Тем не менее, для тестирования нам нужен специальный инструмент. Я нашел grpcurl. Давайте установим его и используем для получения списка доступных сервисов:

grpcurl --plaintext localhost:9090 list   #1-2
  1. Список всех доступных служб gRPC без проверки TLS
  2. Чтобы избежать конфликтов между gRPC и другими каналами, например,, REST, Spring Boot использует другой порт

ch.frankel.blog.grpc.model.HelloService   #1
grpc.health.v1.Health                     #2
grpc.reflection.v1alpha.ServerReflection  #2
  1. Служба gRPC, которую мы определили
  2. Две дополнительные услуги, предоставляемые пользовательским стартером.

Мы также можем углубиться в структуру сервиса:

grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
service HelloService {
  rpc SayHello ( .ch.frankel.blog.grpc.model.HelloRequest ) returns ( .ch.frankel.blog.grpc.model.HelloResponse );
}

Наконец, мы можем вызвать службу с данными:

grpcurl --plaintext -d '{"name": "John"}' localhost:9090 ch.frankel.blog.grpc.model.HelloService/SayHello
{
  "message": "Hello John"
}

Доступ к службе gRPC с помощью обычных инструментов

Представьте, что у нас есть обычное клиентское приложение JavaScript, которому требуется доступ к службе gRPC. Какие могут быть альтернативы?

Общий подход заключается в использовании grpc-web:

<цитата>

Реализация gRPC на JavaScript для клиентов браузера. Дополнительные сведения, в том числе краткое руководство по началу работы, см. в документации по gRPC-web.

gRPC-веб-клиенты подключаются к gRPC-сервисам через специальный прокси; по умолчанию gRPC-web использует Envoy.

Мы ожидаем, что в будущем gRPC-web будет поддерживаться веб-платформами для конкретных языков, такими как Python, Java и Node. Подробнее см. в дорожной карте.

-- grpc-web

В описании указано единственное ограничение: работает только для JavaScript (на данный момент). Однако есть еще один. Это довольно навязчиво. Вам нужно получить файл proto, сгенерировать шаблонный код и сделать так, чтобы ваш код вызывал его. Вы должны сделать это для каждого типа клиентов. Хуже того, если прото-файл изменится, вам придется перегенерировать клиентский код в каждом из них.

Однако существует альтернатива, если вы используете шлюз API. Я опишу, как это сделать с помощью Apache APISIX, но, возможно, другие шлюзы могут сделать то же самое. grpc-transcode — это подключаемый модуль, который позволяет перекодировать вызовы REST в gRPC и обратно.

Первый шаг — зарегистрировать прото-файл в Apache APISIX:

curl http://localhost:9180/apisix/admin/protos/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d "{ "content": "$(sed 's/"/"/g' ../model/src/main/proto/model.proto)" }"

Второй шаг — создать маршрут с помощью вышеуказанного плагина:

curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",                           #1
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",                                       #2
      "service": "ch.frankel.blog.grpc.model.HelloService",  #3
      "method": "SayHello"                                   #4
    }
  },
  "upstream": {
    "scheme": "grpc",
    "nodes": {
      "server:9090": 1
    }
  }
}'
  1. Определить детализированный маршрут
  2. Ссылка на прото-файл, определенный в предыдущей команде
  3. служба gRPC
  4. метод gRPC

На этом этапе любой клиент может отправить HTTP-запрос к указанной конечной точке. Apache APISIX перекодирует вызов gRPC, перенаправит его в указанную службу, получит ответ и снова перекодирует его.

curl localhost:9080/helloservice/sayhello?name=John
{"message":"Hello John"}

По сравнению с grpc-web подход API Gateway позволяет совместно использовать файл proto с одним компонентом: самим Gateway.

Преимущества перекодирования

На этом этапе мы можем использовать возможности шлюза API. Представьте, что нам нужно значение по умолчанию, если не передано name, например,, World. Разработчики с радостью установили бы его в коде, но любое изменение значения потребовало бы полной сборки и развертывания. Изменения могут быть почти мгновенными, если мы поместим значение по умолчанию в цепочку обработки маршрутов шлюза. Давайте соответствующим образом изменим наш маршрут:

curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",
  "plugins": {
    "grpc-transcode": {
      ...
    },
    "serverless-pre-function": {                    #1
      "phase": "rewrite",                           #2
      "functions" : [
        "return function(conf, ctx)                 #3
          local core = require("apisix.core")
          if not ngx.var.arg_name then
            local uri_args = core.request.get_uri_args(ctx)
            uri_args.name = "World"
            ngx.req.set_uri_args(uri_args)
          end
        end"
      ]
    }
  },
  "upstream": {
      ...
  }
}'
  1. Общий универсальный плагин, когда ни один не подходит
  2. Перепишите запрос
  3. Волшебный код Lua, который делает свое дело

Теперь мы можем выполнить запрос с пустым аргументом и получить ожидаемый результат:

curl localhost:9080/helloservice/sayhello?name
{"message":"Hello World"}

Заключение

В этом посте мы кратко описали gRPC и его преимущества для взаимодействия между службами. Мы разработали простую службу gRPC с использованием Spring Boot и grpc-server-spring-boot-starter. Однако за это приходится платить: обычные клиенты не могут получить доступ к услуге. Нам пришлось прибегнуть к grpcurl, чтобы протестировать его. То же самое касается клиентов на основе JavaScript или браузера.

Чтобы обойти это ограничение, мы можем использовать шлюз API. Я продемонстрировал, как настроить Apache APISIX с помощью подключаемого модуля grpc-transcode для достижения желаемого результата.

Полный исходный код для этого поста можно найти на GitHub.

Дальше:

:::информация Первоначально опубликовано на сайте A Java Geek 16 марта 2023 г.

:::


Оригинал