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

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

 Я буду использовать 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?  

Комментарии (12)


  1. furic
    13.07.2021 11:24

    Мой spring boot starter для grpc. https://github.com/LogNet/grpc-spring-boot-starter


    1. deft31 Автор
      13.07.2021 12:13

      Будет круто если то что указано в офф доке этой либы запустится у вас сходу. Здесь больше приведен практический пример, что бы запустить все сходу с минимальным временем.


      1. furic
        13.07.2021 13:01

        У меня все запускается сходу.

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

        Disclaimer: я - автор.


        1. deft31 Автор
          13.07.2021 13:11

          Воу оч круто, если запускается с ходу. Вы сделали просто топовую либу. У нас не у всех с ходу все работало. Хотел бы поучаствовать в этой либе, если есть шанс.


          1. deft31 Автор
            13.07.2021 13:49

            "а не рукоблудство." - что вы подразумеваете под этим?


            1. furic
              13.07.2021 20:31

              Вы вообще соображаете о чем вы пишете? Посмотрел pom вашего проекта - увидел мой стартер в dependencies....


              1. deft31 Автор
                13.07.2021 22:51

                Ээм да. Я и не говорил, что не использую его.


                1. furic
                  13.07.2021 22:56
                  -2

                  Но вы и не говорили что пользуетесь им.... Вы решите - или с нуля или с помощью стартера...


                  1. deft31 Автор
                    14.07.2021 10:33
                    +1

                    Если посмотреть выше и прочитать, что написано в статье. То я указываю какую зависимость использую и говорю что в восторге от этого стартера.


                    1. furic
                      14.07.2021 10:41
                      -1

                      Упс, пропустил. Рад что вам помогло


                      1. deft31 Автор
                        14.07.2021 11:51
                        -1

                        А есть шансы с вами по сотрудничать над стартером?


                      1. furic
                        16.07.2021 21:02

                        Если есть идеи - в discussions на GitHub сайте проекта