gRPC Часть №2 — .NET C# и Google Protobuff. Пишем свой proto-файл.

02.06.2018admin

Несколько дней назад я написал вводную статью, открывающую небольшой сборник записей про связь между вселенной .NET и фреймворком Google Protobuff gRPC. Это вторая часть цикла, по сути также являющаяся вступительной. Сегодня мы научимся создавать свои собственные протофайлы (обычные текстовые файлы с расширением .proto) и изучим синтаксис proto3 (думаю, что версии ниже можно уже не рассматривать).

Итак, как я уже говорил, протофайл — это обычный текстовый формат с определённым строгим синтаксисом, определяющий интерфейс взаимодействия модулей вашего программного обеспечения. Внутри этого файла мы описываем все наши модели данных и методы, оперирующие этими данными. Также стоит помнить, что Google Protobuff является строготипизированным протоколом, а это значит, что нам не придётся беспокоиться за приведение и сохранность данных. Мы будем работать именно с тем, что описали в протофайле.

Необходимо определиться с примитивами синтаксиса протофайла. Это очень простой формат данных и он оперирует всего несколькими сущностями: мета данные, сами методы, сообщения (аналог классов, моделей и т.д.) и списки перечислений (enums). Данного весьма скромного, но мощного набора инструментов хватает, чтобы реализовать, по сути, любую задачу. Начать, конечно, имеет смысл с базовых «головных» настроек протофайла. Под мета данными мы пониманием явное указание синтаксиса, названия сервиса, используемых пакетов (в протофайл можно импортить другие пакеты и протофайлы) и т.д.:

С этой строки должен начинаться наш протофайл. Таким образом мы сообщаем генератору коду о версии синтаксиса нашего .proto. Следующим после этого обязательного поля идут опциональные директивы import и package. Первая позволяет импортировать внешние протофайлы (аналог include), а вторая директива задаёт namespace, в котором будет сгенерирован код. В нашем примере import мы использовать не будем, воспользуемся только определением пространства имён:

Теперь о реализуемой задаче. Для нашего примера я выбрал простенький пример микросервиса для менеджмента пользователей в какой-то абстрактной системе: здесь будут функции добавления, редактирования, удаление пользователей, а также поиск среди них. Это маленький проект, созданный исключительно для демонстрационных целей. Нам предстоит создать методы CreateUser, DeleteUser, UpdateUser, Search, Count (для получения количества пользователей в системе) и два метода RegOnline, RegOffline для соответствующей регистрации наших пользователей. Давайте для начала опишем модель пользователя — пусть это будет некий класс User с полями id, ФИО, датами создания, обновления, дня рождения, булевые типами активный\не активный и онлайн\не онлайн. Также у этой модели будет два enum списка Sex (пол) и Role (роль пользователя в системе). Вполне себе стандартный набор. Давайте определим всё это в нашем протофайле:

Стоит оговорить, что gRPC не умеет возвращать пустые ответы. Мы не сможем создать классическую процедуру с возвращаемым типом void. Также, к сожалению, нельзя сделать метод без аттрибутов. Ваши функции всегда должны что-либо принимать к себе. Поэтому хорошим тоном и best practice именования функций является следующий формат: Foo (сам метод) плюс FooRequest для запроса и FooResponse для ответа. Даже если ничего не нужно передавать — нам необходимо создать пустой экземпляр класса FooRequest. К слову, во второй версии протобуфа была возможность создавать extend message, который транслировались в статические поля, что давало возможность делать подобные вызовы: Foo(FooRequest.Empty);, но в последствии от такого подхода отказались (как и от required и optional полей, почему-то; в целом прото3 стал на порядок меньше и лёгковеснее, чем свои предшественники). Часто создают пустые запросы и ответы: EmptyRequest и EmptyResponse, с помощью которых можно решить наше маленькое неудобство.

Таким образом давайте создадим наш собственный вариант ответа к тем методам, которые мы расцениваем как void. Только мы сделаем сразу хорошо 🙂 Добавим некое подобие обработчика ошибок, статус и сообщение на случай ошибки:

Здесь мы создали вложенный enum с статусами-ответами, а также сообщение на случай ошибки и double timestamp для передачи серверного времени. Ну а почему бы и нет! 🙂

Мы описали главную сущность нашего сервиса. Давайте добавим ещё несколько моделей: коллекцию пользователей, определить тип, в котором мы сможем передавать UserID на сторону микросервиса, заложить возможность поиска и определения общего количества юзеров. Итак, допишем наши proto message:

Ключевое слово repeated позволяет нам создать однотипный список (аналог List в .NET Framework). Кроме списка можно также создать объект Map (аналог Dictionary), в котором хранить пару ключ-значение. Что касается тела сообщения SearchQuery — в нём есть ключевое слово oneof, которое позволяет передавать исключительно либо Role, либо Sex. Таким образом мы можем сделать поиск либо по текстовому содержанию + роль, либо по тексту + пол. Исключительное или.

Когда все модели описаны, самое время перейти к самому вкусному — сигнатуры методов. Каждая функция нашего сервиса должна быть описана внутри конструкции service — название, которое последует после будет интерпретировано генератором в название соответствующего класса, на основе которого мы сможем сделать либо клиент, либо сервер:

Здесь всё очень просто. Это напоминает всем знакомый interface: мы описываем название, приёмный аттрибут и возвращаемое значение. После генерации (об этом я писал в первом статье) на выходе мы будем иметь два абстрактных класса с виртуальными методами: один с постфиксом Base для сервера (в нашем случае: UsersManagementServiceBase) и один с постфиксом Client для клиента (UsersManagementServiceClient).

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

Comments (4)

  • 20

    SirJohn

    01.08.2018 at 08:11

    Только что нашел для себя gRPC. А когда можно ожидать третьей части?

  • 22

    SirJohn

    07.08.2018 at 16:59

    Вторая часть была написана после перовой через полтора месяца. А третья после второй задержалась уже более чем на два. Каждый день проверяю, подписки то тут нет. Пожалуйста….

    1. 23

      koshevoy

      07.08.2018 at 22:50

      Спасибо за ожидание. Дайте мне пару дней 🙂

  • 26

    Aleks

    19.10.2018 at 10:53

    Мы ждёёёммм (^^)

Leave a comment

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Предыдущая запись Следующая запись