Pull to refresh

gRPC сервер с нуля

Reading time5 min
Views28K

Структура проекта будет выглядеть следующим образом:

Структура проекта
Структура проекта

 Я буду использовать maven для сборки проекта. Это важное уточнение, потому как будущая генерация .proto файлов будет отличаться для gradle например. 

   Для написания простого приложения на gRPC мы можем использовать всего одну зависимость:

<dependency>

    <groupId>io.github.lognet</groupId>

    <artifactId>grpc-spring-boot-starter</artifactId>

    <version>4.5.4</version>

</dependency>

Отличный Spring Boot стартер для старта нашего gRPC сервера.   

После загрузки зависимостей, нам потребуется написать .proto файлы. В proto хранятся описания сервисов и ДТО для запроса/ответа. Все пишется интуитивно понятно:

Profile Descriptor. Like DTO.
Profile Descriptor. Like DTO.

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

int64 - для Java это тип Long. Допустим  id профиля будут long.

string - также как и в Java это строковая переменная. 

Отлично у нас есть ДТО для обмена сообщениями. Теперь нам необходим сервис:

Proto Service Example
Proto Service Example

gRPC поддерживает несколько видов взаимодействия клиент/сервер:

  • Запрос-ответ

  • Клиент стрим

  • Сервер стрим

  • Двунаправленный стрим

Подробнее о таком взаимодействии рассказано тут - https://habr.com/en/post/565020/

После того как у нас есть описанный сервис, нам понадобится сгенерировать необходимые классы. Для этого в pom.xml нам в build секцию потребуется добавить плагин:

<build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
                    <outputDirectory>${basedir}/target/generated-sources/grpc-java</outputDirectory>
                    <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.38.0:exe:${os.detected.classifier}</pluginArtifact>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

protoSourceRoot - указание директории, где находятся .proto файлы. 

outputDirectory - указываем директорию, куда будут генерироваться файлы. 

clearOutputDirectory - флаг указывающий не очищать сгенерированные файлы.

После того как вы выполните сборку проекта, можно будет раскрыть папку target и найти следующую структуру:

Generated Structure
Generated Structure

Если вы получили примерно такую структуру, то поздравляю. Все идет по плану и у нас все получается. Если что то пошло не так, то можно посмотреть на код проекта https://github.com/deft1991/petproject/tree/master/grpc-server.

Итак, мы получили сгенерированные сервисы. Теперь нам необходимо их использовать.

Service Structure
Service Structure

Напишем свой GrpcProfileService, который будет наследовать  ProfileServiceGrpc.ProfileServiceImplBase. Прекрасно. У нас готов основной сервис.

Теперь можно переопределять методы, как это сделано на следующем скриншоте : 

Request Response
Request Response

Можно заметить, что над сервисом стоит аннотация GRpcService. Эта аннотация помечает, что класс должен быть зарегистрирован как gRPC бин.

Вернемся к нашему методу. Пока я переопределил всего 1 метод - метод, который получает профиль. Для ответа клиенту мы используем responseObserver. Что бы сообщение ушло на клиент мы вызываем responseObserver.onNext и передаем тело сообщения. В нашем случае это ProfileDescriptorOuterClass.ProfileDescriptor. У всех сгенерированных сообщений сразу генерируется Builder и при помощи него мы можем “сблидить” объект. После отправки сообщения, необходимо отправить responseObserver.onCompleted - уведомление об успешном завершении стрима.

   Для проверки и тестовой отправки сообщений в REST используется обычно Postman. Также с его помощью можно отправить gRPC запросы. Но для меня более удобным является BloomRPC. Ему нужно указать путь к proto файлам и хост, на который отправлять запрос. 

При оправке запроса getCurrentProfile мы получим:

{
  "profile_id": "1",
  "name": "test"
}

Отлично. Давайте двинемся дальше и разберем client stream. При таком типе запросов, клиент может посылать сообщения запросы, а сервер реагировать на каждое из них и/или, при поступлении всех клиентских сообщений, обработать их. В нашем случае предопределенный метод будет выглядеть так:

Client Stream
Client Stream

Мы возвращаем новый StreamObserver клиенту. Так как StreamObserver - это интерфейс, то нам необходимо реализовать его методы. Нас пока интересует метод onNext. В этот метод будут приходить сообщения от клиента. При запуске кода и посылке сообщений от клиента на сервер, можно будет заметить, что счетчик pointCount постоянно увеличивается и в консоль выводится сообщение. 

Server stream. В данном типе запросов, после того как клиент послал сообщение запрос, сервер начинает посылать данные для клиента. Это безумно удобный механизм общения между клиентом и сервером. При помощи данной возможности мы можем рассылать клиентам системные уведомления, события и нотификации. Простой код реализации: 

Server Stream
Server Stream
BloomRPC Stream Responces
BloomRPC Stream Responces

После открытия стрима, сервер посылает 5 ответов. В каком последующем ответе найди профиля увеличивается. 

   После того как мы разобрали стримы с клиента на сервер и сервера на клиент, мы можем приступить к рассмотрению bidirectional streams. В таком типе запросов клиент и сервер могут постоянно обмениваться сообщениями не блокируя друг друга.

 В нашем примере в ответ на сообщение клиента, сервер будет посылать новый профиль с увеличенным счетчиком pointCount. 

   Если у вас возникли сложности с воспроизведением данного примера, то вы можете воспользоваться репозиторием https://github.com/deft1991/petproject/tree/master/grpc-server  , где лежит код приведенный в данной статье. 

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

А вы используете gRPC в проде? Какие у вас ключевые пункты по реализации сервера с использованием gRPC?

Всем приветы. В прошлом посте мы сравнили gRPC и REST. И собственно, прежде чем писать новый сервер на gRPC, давайте попробуем понять нужен ли он нам действительно. Нашей команде была необходима кодогенерация на разные языки программирования. На эту роль неплохо подходил Swagger, Thrift и gRPC со встроенным кодогенератором. От Thrift, спустя какое-то время, пришлось отказаться, из-за его особенностей и сложностей поддержи на c# (по-моему это была основная причина отказа). Дальше был выбор между Swagger + REST и gRPC. В целом оба варианта хороши, но если мы думаем гонять много и часто данные между клиентом и сервером, то почему бы не протестировать gRPC?  

Tags:
Hubs:
Total votes 9: ↑6 and ↓3+3
Comments12

Articles