КулЛиб - Классная библиотека! Скачать книги бесплатно
Всего книг - 712473 томов
Объем библиотеки - 1400 Гб.
Всего авторов - 274472
Пользователей - 125058

Новое на форуме

Новое в блогах

Впечатления

Влад и мир про Владимиров: Ирландец 2 (Альтернативная история)

Написано хорошо. Но сама тема не моя. Становление мафиози! Не люблю ворьё. Вор на воре сидит и вором погоняет и о ворах книжки сочиняет! Любой вор всегда себя считает жертвой обстоятельств, мол не сам, а жизнь такая! А жизнь кругом такая, потому, что сам ты такой! С арифметикой у автора тоже всё печально, как и у ГГ. Простая задачка. Есть игроки, сдающие определённую сумму для участия в игре и получающие определённое количество фишек. Если в

  подробнее ...

Рейтинг: 0 ( 0 за, 0 против).
DXBCKT про Дамиров: Курсант: Назад в СССР (Детективная фантастика)

Месяца 3-4 назад прочел (а вернее прослушал в аудиоверсии) данную книгу - а руки (прокомментировать ее) все никак не доходили)) Ну а вот на выходных, появилось время - за сим, я наконец-таки сподобился это сделать))

С одной стороны - казалось бы вполне «знакомая и местами изьезженная» тема (чуть не сказал - пластинка)) С другой же, именно нюансы порой позволяют отличить очередной «шаблон», от действительно интересной вещи...

В начале

  подробнее ...

Рейтинг: +1 ( 1 за, 0 против).
DXBCKT про Стариков: Геополитика: Как это делается (Политика и дипломатия)

Вообще-то если честно, то я даже не собирался брать эту книгу... Однако - отсутствие иного выбора и низкая цена (после 3 или 4-го захода в книжный) все таки "сделали свое черное дело" и книга была куплена))

Не собирался же ее брать изначально поскольку (давным давно до этого) после прочтения одной "явно неудавшейся" книги автора, навсегда зарекся это делать... Но потом до меня все-таки дошло что (это все же) не "очередная злободневная" (читай

  подробнее ...

Рейтинг: +1 ( 1 за, 0 против).
DXBCKT про Москаленко: Малой. Книга 3 (Боевая фантастика)

Третья часть делает еще более явный уклон в экзотерику и несмотря на все стсндартные шаблоны Eve-вселенной (базы знаний, нейросети и прочие девайсы) все сводится к очередной "ступени самосознания" и общения "в Астралях")) А уж почти каждодневные "глюки-подключения-беседы" с "проснувшейся планетой" (в виде галлюцинации - в образе симпатичной девчонки) так и вообще...))

В общем герою (лишь формально вникающему в разные железки и нейросети)

  подробнее ...

Рейтинг: +1 ( 1 за, 0 против).
Влад и мир про Черепанов: Собиратель 4 (Боевая фантастика)

В принципе хорошая РПГ. Читается хорошо.Есть много нелогичности в механике условий, заданных самим же автором. Ну например: Зачем наделять мечи с поглощением душ и забыть об этом. Как у игрока вообще можно отнять душу, если после перерождении он снова с душой в своём теле игрока. Я так и не понял как ГГ не набирал опыта занимаясь ремеслом, особенно когда служба якобы только за репутацию закончилась и групповое перераспределение опыта

  подробнее ...

Рейтинг: 0 ( 0 за, 0 против).

gRPC: запуск и эксплуатация облачных приложений. Go и Java для Docker и Kubernetes [Индрасири Касун] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Beijing

Boston Farnham Sebastopol

Tokyo

gRPC:

запуск и эксплуатация
облачных приложений

Go и Java для Docker и Kubernetes

Касун Индрасири, Данеш Курупу

2021

ББК 32.988.02-018
УДК 004.738.5
И60

Индрасири Касун, Курупу Данеш
И60 gRPC: запуск и эксплуатация облачных приложений. Go и Java для Docker
и Kubernetes. — СПб.: Питер, 2021. — 224 с.: ил. — (Серия «Бестселлеры
O’Reilly»).
ISBN 978-5-4461-1737-6
Год от года обретая новых сторонников, облачно-ориентированные и микросервисные архитектуры стали основой современного IT. Такой переход значительно повлиял и на структуру
коммуникаций. Теперь приложения часто подключаются друг к другу по сети, и это происходит
с помощью технологий межпроцессной коммуникации. Одной из наиболее популярных и эффективных технологий такого рода является gRPC, но информации о ней не хватает. Так было, пока
не вышла эта книга!
Наконец архитекторы и разработчики смогут подробно разобраться, как технология gRPC
устроена «под капотом», и для этого не придется разгребать десятки устаревших ссылок в поисковике.

16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.)

ББК 32.988.02-018
УДК 004.738.5

Права на издание получены по соглашению с O’Reilly. Все права защищены. Никакая часть данной книги
не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев
авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как
надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не
может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за
возможные ошибки, связанные с использованием книги. Издательство не несет ответственности за доступность материалов, ссылки на которые вы можете найти в этой книге. На момент подготовки книги к изданию
все ссылки на интернет-ресурсы были действующими.

ISBN 978-1492058335 англ.

978-5-4461-1737-6

Authorized Russian translation of the English edition of gRPC: Up and Running
ISBN 9781492058335 © 2020 Kasun Indrasiri and Danesh Kuruppu
This translation is published and sold by permission of O’Reilly Media, Inc.,
which owns or controls all rights to publish and sell the same.
© Перевод на русский язык ООО Издательство «Питер», 2021
© Издание на русском языке, оформление ООО Издательство «Питер»,
2021
© Серия «Бестселлеры O’Reilly», 2021
© Павлов А., перевод с англ. языка, 2020

https://t.me/it_boooks

Краткое содержание
Введение.............................................................................................................. 11
Глава 1. Введение в gRPC...................................................................................... 16
Глава 2. Начало работы с gRPC............................................................................. 37
Глава 3. Методы взаимодействия на основе gRPC................................................. 64
Глава 4. Внутреннее устройство gRPC................................................................... 83
Глава 5. gRPC: расширенные возможности.......................................................... 106
Глава 6. Безопасность в gRPC............................................................................. 141
Глава 7. Использование gRPC в промышленных условиях................................... 165
Глава 8. Экосистема gRPC................................................................................... 198
Об авторах......................................................................................................... 219
Об обложке........................................................................................................ 220

Оглавление
Введение.............................................................................................................. 11
Зачем мы написали эту книгу............................................................................ 11
Целевая аудитория............................................................................................ 12
Структура издания............................................................................................ 12
Использование примеров кода.......................................................................... 13
Условные обозначения...................................................................................... 14
Благодарности................................................................................................... 15
От издательства................................................................................................ 15
Глава 1. Введение в gRPC...................................................................................... 16
Что такое gRPC.................................................................................................. 18
Определение сервиса................................................................................... 19
gRPC-сервер................................................................................................. 21
gRPC-клиент................................................................................................. 23
Обмен сообщениями между клиентом и сервером........................................ 24
Эволюция межпроцессного взаимодействия...................................................... 24
Традиционные подходы к RPC...................................................................... 24
SOAP............................................................................................................ 25
REST............................................................................................................ 25
Появление gRPC........................................................................................... 27

Оглавление   7

Почему стоит выбрать gRPC......................................................................... 28
Сравнение gRPC с другими протоколами: GraphQL и Thrift........................... 31
gRPC в реальных условиях................................................................................ 33
Netflix........................................................................................................... 33
etcd.............................................................................................................. 34
Dropbox........................................................................................................ 35
Резюме.............................................................................................................. 35
Глава 2. Начало работы с gRPC............................................................................. 37
Определение сервиса........................................................................................ 38
Определение сообщений.............................................................................. 39
Определение сервисов................................................................................. 40
Реализация........................................................................................................ 43
Разработка сервиса...................................................................................... 44
Разработка gRPC-клиента............................................................................. 55
Сборка и запуск................................................................................................. 59
Сборка сервера, написанного на Go............................................................. 60
Сборка клиента, написанного на Go............................................................. 60
Запуск сервера и клиента, написанных на Go............................................... 61
Сборка сервера, написанного на Java........................................................... 61
Сборка клиента, написанного на Java........................................................... 61
Запуск сервера и клиента, написанных на Java............................................ 62
Резюме.............................................................................................................. 62
Глава 3. Методы взаимодействия на основе gRPC................................................. 64
Простой (унарный) RPC..................................................................................... 64
Потоковый RPC на стороне сервера................................................................... 67
Потоковый RPC на стороне клиента................................................................... 71
Двунаправленный потоковый RPC..................................................................... 74
Взаимодействие микросервисов на основе gRPC................................................ 80
Резюме.............................................................................................................. 82

8  

Оглавление

Глава 4. Внутреннее устройство gRPC................................................................... 83
Процесс передачи сообщений в RPC.................................................................. 84
Кодирование сообщений с помощью Protocol Buffers......................................... 86
Методики кодирования................................................................................. 90
Обрамление сообщений с префиксом длины..................................................... 93
gRPC поверх HTTP/2.......................................................................................... 95
Запрос.......................................................................................................... 96
Ответ........................................................................................................... 98
Передача сообщений с помощью разных методов взаимодействия
на основе gRPC.......................................................................................... 100
Практическая реализация архитектуры gRPC.................................................. 104
Резюме............................................................................................................ 105
Глава 5. gRPC: расширенные возможности.......................................................... 106
Перехватчики.................................................................................................. 106
Серверные перехватчики........................................................................... 107
Клиентские перехватчики.......................................................................... 112
Крайние сроки................................................................................................. 116
Механизм отмены............................................................................................ 120
Обработка ошибок........................................................................................... 121
Мультиплексирование..................................................................................... 126
Метаданные.................................................................................................... 128
Создание и извлечение метаданных........................................................... 129
Отправка и получение метаданных на стороне клиента............................. 130
Отправка и получение метаданных на стороне сервера............................. 132
Сопоставление имен.................................................................................. 134
Балансировка нагрузки.................................................................................... 135
Прокси-сервер для балансировки нагрузки................................................ 136
Балансировка нагрузки на стороне клиента............................................... 137
Сжатие....................................................................................................... 139
Резюме............................................................................................................ 140

Оглавление   9

Глава 6. Безопасность в gRPC............................................................................. 141
Аутентификация gRPC-канала с помощью TLS................................................. 141
Однонаправленное защищенное соединение............................................. 142
Включение безопасного соединения mTLS................................................. 146
Аутентификация вызовов в gRPC..................................................................... 151
Использование базовой аутентификации................................................... 152
Использование OAuth 2.0........................................................................... 157
Использование JWT.................................................................................... 161
Аутентификация в Google Cloud с использованием токенов........................ 162
Резюме............................................................................................................ 163
Глава 7. Использование gRPC в промышленных условиях................................... 165
Тестирование gRPC-приложений..................................................................... 165
Тестирование gRPC-сервера....................................................................... 165
Тестирование gRPC-клиента....................................................................... 167
Нагрузочное тестирование......................................................................... 169
Непрерывная интеграция........................................................................... 170
Развертывание................................................................................................ 170
Развертывание в Docker............................................................................. 171
Развертывание в Kubernetes....................................................................... 173
Наблюдаемость............................................................................................... 180
Метрики..................................................................................................... 180
Журнальные записи................................................................................... 190
Трассировка............................................................................................... 191
Отладка и устранение неполадок.................................................................... 195
Резюме............................................................................................................ 196
Глава 8. Экосистема gRPC................................................................................... 198
gRPC-шлюз...................................................................................................... 198
Перекодирование из HTTP/JSON в gRPC.......................................................... 206
Протокол отражения gRPC-сервера................................................................. 207

10   Оглавление
gRPC Middleware.............................................................................................. 210
Протокол для проверки работоспособности..................................................... 213
grpc_health_probe............................................................................................ 215
Другие проекты экосистемы gRPC.................................................................... 217
Резюме............................................................................................................ 217
Об авторах......................................................................................................... 219
Об обложке........................................................................................................ 220

Введение
В наши дни приложения часто «общаются» между собой по компьютерным
сетям, используя технологии межпроцессного взаимодействия. gRPC — это
разновидность межпроцессного взаимодействия, основанная на высокопроизводительных удаленных вызовах процедур (remote procedure calls, RPC)
и предназначенная для создания распределенных приложений и микросервисов. Благодаря появлению микросервисов и облачно-ориентированных
приложений популярность gRPC растет экспоненциально.

Зачем мы написали эту книгу
Наблюдая за распространением gRPC, мы чувствовали, что разработчикам
необходимо полномасштабное руководство по данной технологии, которое
можно использовать в качестве основного справочника на каждом этапе
цикла разработки gRPC-приложений. На просторах Интернета gRPC посвящено множество материалов и примеров кода (документация, блоги,
статьи, презентации и т. д.), но при этом отсутствует единый ресурс, пригодный для создания полноценных приложений. К тому же очень сложно
найти информацию о внутреннем устройстве протокола gRPC и о том, как
он работает.
Чтобы исправить ситуацию, мы написали эту книгу. Она поможет вам разобраться в основах gRPC, понять отличия данного протокола от традиционных
средств межпроцессного взаимодействия, изучить практические методы
коммуникации по gRPC и научиться создавать gRPC-приложения на Go
и Java. Здесь вы узнаете, как устроена эта технология, как развертывать
gRPC-приложения в промышленных условиях и как gRPC интегрируется
в Kubernetes и облачную экосистему в целом.

12   Введение

Целевая аудитория
Эта книга ориентирована прежде всего на разработчиков, которые занимаются созданием распределенных приложений и микросервисов с помощью
разных технологий межпроцессного взаимодействия. Такие разработчики
должны разбираться в основах gRPC, понимать, когда и как следует использовать данный протокол, иметь представление о рекомендуемых методиках
развертывания gRPC-сервисов в промышленных условиях и т. д. Кроме того,
представленный материал будет весьма полезен архитекторам, которые
внедряют микросервисную или облачно-ориентированную архитектуру
и проектируют механизмы взаимодействия сервисов: gRPC сравнивается
с аналогичными технологиями и даются рекомендации о том, когда его стоит
использовать, а когда — нет.
Мы исходим из того, что разработчики и архитекторы, читающие данную
книгу, имеют общее представление о распределенных вычислениях, включая
методы межпроцессного взаимодействия, сервис-ориентированную архитектуру (service-oriented architecture, SOA) и микросервисы.

Структура издания
Эта книга написана так, что теоретический материал объясняется на примерах из реальной жизни. Мы активно используем демонстрационный
код на Go и Java, чтобы читатель мог сразу получить практический опыт
применения концепций, с которыми знакомится. Мы разделили эту книгу
на восемь глав.
‰‰ В главе 1 вы получите общее представление об основах gRPC и сможете
сравнить эту технологию с аналогичными разновидностями межпроцессного взаимодействия, такими как REST, GraphQL и прочими механизмами RPC.
‰‰ На страницах главы 2 вы получите свой первый практический опыт создания полноценного gRPC-приложения на Go или Java.
‰‰ В главе 3 вы исследуете методы работы с gRPC на реальных примерах.
‰‰ Если вы уже имеете опыт использования gRPC и хотите узнать, как устроена эта технология, то глава 4 для вас. Здесь мы пройдемся по каждому

Использование примеров кода   13

этапу взаимодействия сервера и клиента и покажем, как gRPC работает
на уровне сети.
‰‰ В главе 5 вы изучите некоторые из самых важных расширенных возможностей gRPC, такие как использование перехватчиков, работа с метаданными, мультиплексирование, балансировка нагрузки и т. д.
‰‰ В главе 6 вы досконально изучите методы защиты коммуникационных
каналов и узнаете, как аутентифицировать пользователей и управлять
их доступом к gRPC-приложениям.
‰‰ В главе 7 мы пройдемся по всему циклу разработки gRPC-приложений.
Будут рассмотрены тестирование, интеграция с CI/CD, развертывание
и запуск в Docker и Kubernetes, а также мониторинг.
‰‰ В главе 8 мы обсудим некоторые полезные вспомогательные компоненты,
разработанные с расчетом на gRPC. Большинство из этих проектов подходит для создания реальных gRPC-приложений.

Использование примеров кода
Все примеры кода и дополнительные материалы доступны для скачивания
по адресу grpc-up-and-running.github.io. Мы настоятельно рекомендуем использовать код, представленный в данном репозитории, по ходу чтения.
Это позволит вам лучше понять концепции, с которыми вы знакомитесь.
Эти примеры кода поддерживаются и обновляются с учетом последних
версий библиотек, зависимостей и инструментов разработки. Иногда содержимое репозитория может немного отличаться от кода, приведенного
в книге. Если вы заметили некие расхождения (как отрицательные, так
и положительные) в наших листингах, то мы очень просим вас оформить
запрос на включение изменений (pull request, PR).
Вы можете использовать представленные здесь примеры кода в собственных программах и документации. Если вы не воспроизводите существенную часть кода, то связываться с нами не нужно. Это, например, касается
ситуаций, когда вы включаете в свою программу несколько фрагментов
кода, которые приводятся в данной книге. Однако на продажу или распространение примеров из книг издательства O’Reilly требуется отдельное
разрешение. Цитировать книгу и приводить примеры кода при ответе на

14   Введение
вопрос вы можете свободно. Но если хотите включить существенную часть
приведенного здесь кода в документацию своего продукта, то вам следует
связаться с нами.
Мы приветствуем, но не требуем отсылки на оригинал. Отсылка обычно состоит из названия, имени автора, издательства, ISBN и копирайта. Например:
«gRPC: запуск и эксплуатация облачных приложений. Go и Java для Docker
и Kubernetes», Касун Индрасири и Данеш Куруппу (Питер). Copyright 2020
Kasun Indrasiri and Danesh Kuruppu, 978-5-4461-1737-6.
Если вам кажется, что ваше обращение с примерами кода выходит за рамки добросовестного использования или условий, перечисленных выше, то
можете обратиться к нам по адресу permissions@oreilly.com.

Условные обозначения
В этой книге используются следующие условные обозначения.
Курсив
Курсивом выделены новые термины.
Моноширинный шрифт

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

Показывает команды или другой текст, который пользователь должен
ввести самостоятельно.
Моноширинный курсивный шрифт

Показывает текст, который должен быть заменен значениями, введенными
пользователем, или значениями, определяемыми контекстом.
Шрифт без засечек

Используется для обозначения URL, адресов электронной почты, названий кнопок, каталогов.

От издательства   15

Этот рисунок указывает на совет или предложение.

Такой рисунок указывает на общее замечание.

Этот рисунок указывает на предупреждение.

Благодарности
Выражаем признательность научным редакторам этой книги, Жюльену
Андрю, Тиму Реймонду и Райану Микеле. Кроме того, мы хотели бы поблагодарить старшего редактора Мелиссу Поттер за ее советы и поддержку,
а также нашего редактора Райана Шоу за всю оказанную им помощь. И конечно же, мы говорим спасибо всему сообществу gRPC, которое создало
этот замечательный проект с открытым исходным кодом.

От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com
(издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию
о наших книгах.

ГЛАВА 1

Введение в gRPC
Современные приложения редко работают в изоляции. Большинство из них
общаются друг с другом по сети и координируют свои действия, обмениваясь
сообщениями. Таким образом, современная программная система представляет собой набор распределенных приложений, которые работают на разных
участках сети и взаимодействуют с помощью различных коммуникационных
протоколов. Например, интернет-магазин может состоять из нескольких
распределенных компонентов, таких как система управления заказами, каталог, база данных и т. д. Для реализации бизнес-возможностей подобного
интернет-магазина необходимо наладить связь между этими компонентами.
Микросервисная архитектура
Микросервисная архитектура заключается в создании приложений
на основе набора отдельных, автономных (с точки зрения разработки,
развертывания и масштабирования), бизнес-ориентированных и слабо
связанных между собой сервисов1.

С появлением микросервисной (oreil.ly/q6N1P) и облачно-ориентированной
(oreil.ly/8Ow2T) архитектуры традиционные приложения, обладающие разными бизнес-возможностями, подверглись разделению на более мелкие,
автономные и бизнес-ориентированные сущности, известные как микросервисы. И эти микросервисы должны связываться друг с другом по сети,
используя методики межпроцессного (межсервисного, межпрограммного)
взаимодействия (inter-process communication, IPC). Если бы интернет-магазин из предыдущего примера был реализован с помощью микросервисной
архитектуры, то состоял бы из нескольких взаимосвязанных микросервисов,
которые бы отвечали за управление, поиск, оформление покупки, достав1

Indrasiri K., Siriwardena P. Microservices for the Enterprise. — Apress, 2018.

Глава 1. Введение в gRPC   17

ку и т. д. По сравнению с работой традиционных приложений данный подход
требует большего количества сетевых соединений, поскольку задействовано
множество мелких компонентов. Поэтому, какой бы архитектурный стиль
вы ни выбрали (традиционный или на основе микросервисов), методы межпроцессного взаимодействия — один из важнейших аспектов современных
распределенных приложений.
Межпроцессное взаимодействие обычно реализуется путем обмена сообщениями в асинхронном стиле (на основе запросов и ответов или событийной
модели). Если взаимодействие происходит синхронно, то клиентский процесс
шлет по сети сообщение серверному процессу и ждет ответа. При асинхронном событийном подходе процессы обмениваются асинхронными сообщениями, используя посредника, известного как брокер событий. Выбор метода
взаимодействия зависит от бизнес-требований.
Что касается реализации синхронного взаимодействия в виде запросов
и ответов, в современных облачно-ориентированных приложениях и микросервисах для этого чаще всего используют уже устоявшийся архитектурный
стиль REST. Ваше приложение или сервис состоит из набора ресурсов, для
чтения и модификации которых применяются сетевые вызовы, отправляемые
по протоколу HTTP. Однако в большинстве случаев сервисы REST довольно громоздки, неэффективны и подвержены ошибкам в ходе построения
межпроцессного взаимодействия. Часто возникает необходимость в хорошо
масштабируемой технологии IPC со слабой связанностью, которая была бы
более эффективной по сравнению с REST. Именно эту роль берет на себя
gRPC — современный стиль межпроцессного взаимодействия для создания
распределенных приложений и микросервисов (мы сравним gRPC и REST
позже в данной главе). gRPC в основном использует синхронные запросы
и ответы, но вместе с тем имеет полностью асинхронный или потоковый
режим, доступный после установления соединения.
В настоящей главе мы расскажем, что такое gRPC, и объясним причины,
приведшие к созданию данного протокола межпроцессного взаимодействия.
Мы рассмотрим его ключевые составляющие, используя примеры из реальной жизни. Чтобы понимать основные проблемы, которые пытается решить
gRPC, необходимо иметь четкое представление о методах IPC и о том, как
они эволюционировали со временем. Поэтому мы разберем все эти методы
и сравним их друг с другом. И начнем с разговора о том, что собой представляет gRPC.

18   Глава 1. Введение в gRPC

Что такое gRPC
gRPC (значение буквы g меняется с каждой новой версией (oreil.ly/IKCi3)) —
технология межпроцессного взаимодействия, позволяющая соединять,
вызывать, администрировать и отлаживать распределенные гетерогенные
приложения в стиле, который по своей простоте мало чем отличается от
вызова локальных функций.
При разработке gRPC-приложения первым делом нужно определить интерфейс сервисов. Данное определение содержит информацию о том, как
потребители могут обращаться к вашим сервисам, какие методы можно
вызывать удаленно, какие параметры и форматы сообщений следует применять при вызове этих методов и т. д. Язык, который используется в спе­
цификации сервиса, называется языком описания интерфейсов (interface
definition language, IDL).
На основе спецификации сервиса можно сгенерировать серверный код
(или серверный каркас), который упрощает создание логики на серверной стороне за счет предоставления абстракций для низкоуровневого взаимодействия.
Вы также можете сгенерировать клиентский код (или клиентскую заглушку),
инкапсулирующий низкоуровневые аспекты коммуникационного протокола в разных языках программирования. Методы, указанные в определении
интерфейса сервиса, можно вызывать удаленно на клиентской стороне по
примеру того, как вызываются локальные функции. Внутренний фреймворк
gRPC возьмет на себя все сложные аспекты, присущие соблюдению строгих
контрактов между сервисами, сериализации данных, сетевых коммуникаций,
аутентификации, контролю доступа, наблюдаемости и т. д.
Понять основополагающие концепции gRPC попробуем на реальном примере использования микросервисов, основанных на этой технологии. Допустим,
мы занимаемся разработкой приложения для розничной торговли, состоящего из нескольких микросервисов, и хотим, чтобы один из них возвращал
описание товаров, доступных для продажи, как показано на рис. 1.1 (в главе 2
мы реализуем данный сценарий с нуля). Сервис ProductInfo спроектирован
так, чтобы к нему был доступ в сети по протоколу gRPC.
Определение сервиса находится в файле ProductInfo.proto, который используется для генерации кода как на серверной, так и на клиентской стороне.

Что такое gRPC   19

В этом примере мы исходим из того, что сервис написан на языке Go, а потребитель — на Java. Сетевое взаимодействие обоих объектов происходит
по HTTP/2.

Рис. 1.1. Микросервис и потребитель на основе gRPC

Теперь рассмотрим подробности данного взаимодействия. Первый шаг
при создании gRPC-сервиса — определение его интерфейса, содержащее
описание методов, которые он предоставляет, а также их входящих параметров и типов возвращаемых значений. Обсудим этот процесс более
подробно.

Определение сервиса
В качестве IDL для определения интерфейса сервисов gRPC использует
Protocol Buffers (https://oreil.ly/iFi-b) — расширяемый механизм сериализации
структурированных данных, который не зависит от языка и платформы (мы
подробно рассмотрим его в главе 4, а пока вам достаточно знать, что это
средство сериализации). Определение интерфейса сервиса хранится в protoфайле — обычном текстовом файле с расширением .proto. gRPC-сервисы
описываются в стандартном формате Protocol Buffers; при этом параметры
методов RPC и типы возвращаемых значений указываются в виде сообщений. Поскольку определение сервисов — расширение спецификации Protocol

20   Глава 1. Введение в gRPC
Buffers, генерация кода из вашего proto-файла возможна при наличии специального дополнения gRPC.
В нашем демонстрационном примере интерфейс сервиса ProductInfo в формате Protocol Buffers выглядит так, как показано в листинге 1.1. Определение
сервиса состоит из определения интерфейса, в котором указаны удаленные
методы, их входящие и исходящие параметры, а также определения типов
(или форматы сообщений) этих параметров.
Листинг 1.1. Определение gRPC-сервиса ProductInfo с помощью Protocol Buffers
// ProductInfo.proto
syntax = "proto3"; 
package ecommerce; 
service ProductInfo { 
rpc addProduct(Product) returns (ProductID);
rpc getProduct(ProductID) returns (Product);
}




message Product { 
string id = 1; 
string name = 2;
string description = 3;
}
message ProductID { 
string value = 1;
}

 Определение сервиса начинается с указания версии Protocol Buffers
(proto3), которую мы используем.
 Имена пакетов позволяют предотвратить конфликты имен между типами
сообщений и также применяются для генерации кода.
 Определение интерфейса gRPC-сервиса.
 Удаленный метод для добавления товара, который возвращает ID этого
товара в качестве ответа.
 Удаленный метод для получения товара по его ID.
 Определение формата/типа сообщений Product.

Что такое gRPC   21

 Поле (пара «имя — значение»), хранящее ID товара. Обладает уникальным
номером, с помощью которого его можно идентифицировать в двоичном
формате сообщений.
 Пользовательский тип для идентификационного номера товара.
Таким образом, сервис — это набор методов (например, addProduct и getProduct),
которые можно вызывать удаленно. У каждого метода есть тип возвращаемых значений, либо определяющийся в рамках самого сервиса, либо
импортирующийся в определение Protocol Buffers.
Входящие и возвращаемые параметры могут иметь пользовательские типы
(например, Product и ProductID) или стандартные типы Protocol Buffers
(https://oreil.ly/0Uc3A), описанные в определении сервиса. Тип состоит из
сообщений, каждое из которых представляет собой небольшой логический
блок информации с набором пар «имя — значение» (мы называем их полями). Каждое поле имеет уникальный номер (например, string id = 1), с помощью которого оно идентифицируется в двоичном формате сообщений.
На основе этого определения сервиса генерируется серверная и клиентская
части вашего gRPC-приложения. В следующем подразделе мы подробно
поговорим о реализации gRPC-сервера.

gRPC-сервер
Созданное вами определение сервиса можно использовать для генерации
серверного или клиентского кода (а также стандартного кода Protocol
Buffers). Для этого в Protocol Buffers предусмотрен компилятор protoc.
На серверной стороне для обработки клиентских вызовов используется
gRPC-сервер. Таким образом, чтобы сервис ProductInfo мог выполнять свою
работу, вы должны сделать следующее.
1. Реализовать логику сервиса на основе сгенерированного каркаса, переопределив его базовый класс.
2. Запустить gRPC-сервер, чтобы принимать запросы от клиентов и возвращать ответы сервиса.
При реализации логики сервиса первым делом нужно сгенерировать его
каркас на основе его же определения. Например, в листинге 1.2 можно

22   Глава 1. Введение в gRPC
видеть удаленные функции, сгенерированные для сервиса ProductInfo на
языке Go. В теле каждой из этих удаленных функций можно реализовать
ее логику.
Листинг 1.2. Серверная реализация сервиса ProductInfo на языке Go
import (
...
"context"
pb "github.com/grpc-up-and-running/samples/ch02/productinfo/go/proto"
"google.golang.org/grpc"
...
)
// реализация ProductInfo на Go
// удаленный метод для добавления товара
func (s *server) AddProduct(ctx context.Context, in *pb.Product) (
*pb.ProductID, error) {
// бизнес-логика
}
// удаленный метод для получения товара
func (s *server) GetProduct(ctx context.Context, in *pb.ProductID) (
*pb.Product, error) {
// бизнес-логика
}

Закончив с реализацией сервиса, вы должны запустить gRPC-сервер, который будет прослушивать клиентские запросы, перенаправлять их сервису
и возвращать его ответы клиенту. Фрагмент кода в листинге 1.3 демонстрирует реализацию gRPC-сервера на Go для работы с сервисом ProductInfo.
Здесь мы открываем TCP-порт, запускаем gRPC-сервер и регистрируем на
нем сервис ProductInfo.
Листинг 1.3. Запуск gRPC-сервера для сервиса ProductInfo,
написанного на Go
func main() {
lis, _ := net.Listen(„tcp", port)
s := grpc.NewServer()
pb.RegisterProductInfoServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf(„failed to serve: %v", err)
}
}

Что такое gRPC   23

Это все, что необходимо сделать на серверной стороне. Теперь перейдем
к реализации клиента gRPC.

gRPC-клиент
С помощью определения сервиса можно сгенерировать не только серверный
каркас, но и клиентскую заглушку. Она предоставляет те же методы, что
и сервер, но при этом позволяет вызывать клиентский код; далее методы
транслируются в удаленные (сетевые) вызовы функций, направленные
к серверной стороне. Поскольку определения gRPC-сервиса не зависят от
языка, вы можете сгенерировать клиентский и серверный код для любой
поддерживаемой среды выполнения (с помощью сторонних реализаций
(oreil.ly/psi72)) на ваш выбор. В нашем случае мы генерируем для сервиса
ProductInfo заглушку на Java и серверный каркас на Go. Клиентский код
представлен в листинге 1.4. Независимо от выбранного нами языка, реализация клиентской стороны состоит из установления соединения с удаленным
сервером, подключения клиентской заглушки к этому соединению и вызова
с ее помощью удаленных методов.
Листинг 1.4. gRPC-клиент для удаленного вызова методов сервиса
// создаем канал, используя адрес удаленного сервера
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext(true)
.build();
// инициализируем на основе этого канала блокирующую заглушку
ProductInfoGrpc.ProductInfoBlockingStub stub =
ProductInfoGrpc.newBlockingStub(channel);
// вызываем удаленный метод с помощью блокирующей заглушки
StringValue productID = stub.addProduct(
Product.newBuilder()
.setName("Apple iPhone 11")
.setDescription("Meet Apple iPhone 11." +
"All-new dual-camera system with " +
"Ultra Wide and Night mode.")
.build());

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

24   Глава 1. Введение в gRPC

Обмен сообщениями между клиентом и сервером
Когда gRPC-клиент обращается к gRPC-сервису, его gRPC-библиотека
выполняет удаленный вызов процедуры по HTTP/2, используя Protocol
Buffers и маршалинг. Серверная сторона распаковывает полученный запрос
и вызывает соответствующую процедуру с помощью Protocol Buffers. Затем
аналогичным образом сервер возвращает ответ клиенту. Для передачи данных
gRPC задействует HTTP/2 — высокопроизводительный двоичный протокол
обмена сообщениями с поддержкой двунаправленности. В главе 4 мы обсудим
низкоуровневые аспекты передачи сообщений между клиентами и серверами,
а также Protocol Buffers и то, как gRPC использует HTTP/2.
Маршалинг — процесс преобразования параметров и удаленной функции в пакет, который отправляется по сети. В результате данный пакет
преобразуется в вызов метода.

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

Эволюция межпроцессного взаимодействия
С момента появления методы межпроцессного взаимодействия претерпели
радикальные изменения. Мы можем наблюдать возникновение различных
методик, направленных на удовлетворение современных нужд и предоставление лучших и более эффективных подходов к разработке. Поэтому
важно, чтобы вы хорошо ориентировались в истории развития технологий
IPC и понимали, как они в итоге эволюционировали в gRPC. Рассмотрим
некоторые из наиболее популярных методик и сравним их с gRPC.

Традиционные подходы к RPC
Технология RPC была популярной разновидностью межпроцессного взаи­
модействия для создания клиент-серверных приложений. Благодаря ей
удаленный вызов функций или методов ничем не отличался от локального.
Среди ранних реализаций RPC особой популярностью пользовались CORBA

Эволюция межпроцессного взаимодействия   25

(Common Object Request Broker Architecture — общая архитектура брокера
объектных запросов) и Java RMI (Remote Method Invocation — удаленный
вызов методов); они использовались для создания и соединения сервисов
и приложений. Однако большинство таких традиционных реализаций
RPC страдали от излишней сложности, поскольку были построены поверх
коммуникационных протоколов, таких как TCP, имеющих раздутые специ­
фикации, которые затрудняли взаимодействие.

SOAP
Ввиду ограничений, присущих традиционным реализациям RPC наподобие
CORBA, крупные корпорации, такие как Microsoft и IBM, разработали и начали активно продвигать SOAP (Simple Object Access Protocol — простой
протокол доступа к объектам). Это стандартный подход к взаимодействию
в сервис-ориентированной архитектуре (service-oriented architecture, SOA),
который позволял сервисам (в контексте SOA их обычно называют вебсервисами) обмениваться структурированными данными в формате XML;
в качестве транспортного протокола чаще всего использовался HTTP.
Технология SOAP позволяла определить интерфейс сервиса и его операции,
а также формат сообщений на основе XML, который будет применяться для
вызова этих операций. Она была достаточно популярной, однако высокая
сложность формата сообщений и спецификации, разработанной вокруг
нее, препятствовала гибкому построению распределенных приложений.
Поэтому с точки зрения современной разработки распределенных решений
веб-сервисы SOAP считаются устаревшей технологией. В наши дни вместо
нее обычно используют архитектурный стиль REST.

REST
REST (Representational State Transfer — передача состояния представления) — архитектурный стиль, который впервые предложил Рой Филдинг
в своей докторской диссертации (oreil.ly/6tRrt). Филдинг был одним из
главных авторов спецификации HTTP. REST — основа ресурсно-ориентированной архитектуры (resource-oriented architecture, ROA); согласно
ей распределенные приложения моделируются в виде набора ресурсов,
с которыми могут взаимодействовать (читать, создавать, обновлять и удалять) клиенты.

26   Глава 1. Введение в gRPC
Фактическая реализация REST — протокол HTTP, позволяющий смоделировать приложение, которое состоит из набора REST-ресурсов, доступных
по уникальному идентификатору (URL). Для выполнения операций, изменяющих состояние этих ресурсов, используются HTTP-методы (GET,
POST, PUT, DELETE, PATCH и т. д.). Состояние ресурса представлено
в текстовом формате, таком как JSON, XML, HTML, YAML и т. д.
Построение приложений с помощью архитектурного стиля REST вкупе
с HTTP и JSON стало фактическим стандартом для создания микросервисов. Тем не менее, когда таких микросервисов, взаимодействующих по
сети, становится слишком много, REST перестает удовлетворять современным требованиям. Он имеет несколько важных ограничений, которые
не позволяют использовать его в качестве протокола обмена сообщениями
в современных приложениях, основанных на микросервисах.

Неэффективные текстовые форматы и протоколы
В основе REST лежат текстовые транспортные протоколы, такие как
HTTP 1.x, и текстовые форматы наподобие JSON, понятные человеку.
Это делает взаимодействие сервисов довольно неэффективным, поскольку
они обмениваются плохо оптимизированными сообщениями.
Клиентское приложение (отправитель) генерирует двоичные структурированные данные, затем переводит их в текстовый вид (поскольку в HTTP 1.x
передаваемые сообщения должны быть текстовыми) и отправляет по сети
(по HTTP) серверу (адресату), а тот, в свою очередь, анализирует их и превращает обратно в двоичную структуру. Вместо этого мы могли бы послать
сообщение в двоичном формате, который можно привязать к бизнес-логике
сервиса и потребителя. Один из популярных аргументов в пользу формата
JSON — его проще применять, ведь он понятен человеку. Данная проблема
относится скорее к инструментарию, чем к двоичным протоколам.

Нехватка сильно типизированных интерфейсов между приложениями
Современные приложения состоят из большого количества сервисов, взаимодействующих по сети и основанных на совершенно разных технологиях
и языках. В таких условиях отсутствие четко заданных и сильно типизированных определений сервисов может стать существенным недостатком.
Большинство существующих средств определения сервисов в REST, таких
как OpenAPI/Swagger, были созданы задним числом, поэтому им не хва-

Эволюция межпроцессного взаимодействия   27

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

Разработчикам сложно гарантировать соблюдение архитектурного стиля REST
Для создания REST-сервисов необходимо применять множество «общепринятых методик». Но эти методики не являются частью протоколов (таких
как HTTP), поэтому обеспечить соблюдение архитектурного стиля на стадии реализации не слишком просто. Как следствие, большинство сервисов,
которые якобы основаны на REST, не соблюдают данный стиль в полной
мере. Многие из них на самом деле — всего лишь HTTP-сервисы, доступные
по сети. Поэтому разработчикам приходится тратить много времени на то,
чтобы сделать свои сервисы согласованными и совместимыми с REST.
Все эти ограничения, возникающие при создании современных облачноориентированных приложений на основе традиционных методов IPC, послужили толчком к изобретению более подходящего протокола для обмена
сообщениями.

Появление gRPC
В прошлом для соединения тысяч микросервисов, расположенных в разных
вычислительных центрах и созданных на основе различных технологий,
компания Google использовала RPC-фреймворк общего назначения Stubby
(oreil.ly/vat5r). Основной его слой RPC был рассчитан на обработку десятков
миллиардов запросов в секунду в масштабах всего Интернета. Несмотря
на множество отличных качеств, проект Stubby слишком сильно привязан
к внутренней инфраструктуре Google, поэтому так и не стал общепринятым
стандартом.

28   Глава 1. Введение в gRPC
В 2015 году компания Google выпустила (oreil.ly/cUZSG) RPC-фреймворк
с открытым исходным кодом под названием gRPC, который предоставляет
стандартизированную кросс-платформенную RPC-инфраструктуру общего
назначения. Этот проект был создан для того, чтобы сделать масштабируемость, производительность и функциональность, присущие Stubby, доступными широкому кругу пользователей.
На протяжении последних нескольких лет популярность gRPC росла взрывными темпами. Крупномасштабное внедрение этой технологии было проведено такими компаниями, как Netflix, Square, Lyft, Docker, Cisco и CoreOS.
Позже она присоединилась (https://oreil.ly/GFffo) к CNCF (Cloud Native
Computing Foundation) — одному из самых популярных фондов открытого
программного обеспечения, который ставит перед собой задачу сделать
облачно-ориентированные вычисления универсальными и стабильными;
проект gRPC значительно укрепился благодаря экосистеме CNCF.
Теперь рассмотрим ряд ключевых аспектов gRPC, которые делают эту технологию более привлекательной в сравнении с традиционными протоколами
межпроцессного взаимодействия.

Почему стоит выбрать gRPC
gRPC — технология межпроцессного взаимодействия, способная работать
в масштабах Интернета и лишенная большинства недостатков, присущих
традиционным средствам IPC. Благодаря своим сильным сторонам онавсе
чаще применяется в современных приложениях и серверах. Но что делает ее
особенной на фоне многих других коммуникационных протоколов? Обсудим
некоторые ключевые преимущества gRPC более подробно.

Преимущества gRPC
Преимущества, предоставляемые технологией gRPC, — залог ее популярности. Некоторые из них перечислены ниже.
‰‰ Высокая эффективность. Вместо текстовых форматов, таких как JSON
или XML, для взаимодействия с сервисами и клиентами gRPC использует двоичный протокол на основе Protocol Buffers. Кроме того, Protocol
Buffers в gRPC работает поверх HTTP/2, что еще больше ускоряет межпроцессное взаимодействие. Благодаря этому gRPC — одна из самых
эффективных технологий IPC.

Эволюция межпроцессного взаимодействия   29

‰‰ Простые, четкие интерфейсы и форматы сообщений. gRPC исповедует
контрактный подход к разработке приложений. Сначала определяются
интерфейсы сервисов, и только потом начинается работа над их реализацией. Поэтому, в отличие от OpenAPI/Swagger (для REST-сервисов)
и WSDL (для веб-сервисов SOAP), gRPC делает процесс разработки
приложений простым, но в то же время согласованным, надежным
и масштабируемым.
‰‰ Сильная типизация. Поскольку для определения gRPC-сервисов используется Protocol Buffers, их контракты однозначно определяют типы
данных, которыми будут обмениваться приложения. Это делает разработку распределенных приложений куда более стабильной, поскольку
статическая типизация позволяет избежать большинства ошибок выполнения и совместимости, возникающих в ситуациях, когда облачноориентированные проекты создаются сразу несколькими командами и с
помощью разных технологий.
‰‰ Многоязычие. Протокол gRPC рассчитан на поддержку многих языков
программирования. Protocol Buffers позволяет определять gRPC-сервисы
без привязки к конкретному языку. Поэтому вы можете взаимодействовать с любым существующим gRPC-сервисом или клиентом, используя
тот язык, который вам нравится.
‰‰ Двунаправленная потоковая передача. gRPC имеет встроенную поддержку
потоковой передачи на стороне клиента и сервера, интегрированную непосредственно в сервис. Это существенно упрощает разработку потоковых
сервисов и клиентов. Возможность реализовывать как традиционный
обмен сообщениями вида «запрос — ответ», так и потоковую передачу
данных между клиентом и сервером — ключевое преимущество gRPC
перед традиционным архитектурным стилем REST.
‰‰ Встроенные практичные возможности. gRPC имеет встроенную поддержку таких часто используемых возможностей, как аутентификация,
шифрование, устойчивость (крайние сроки, время ожидания), обмен метаданными, сжатие, балансировка нагрузки, обнаружение сервисов и т. д.
(мы исследуем их в главе 5).
‰‰ Интеграция с облачно-ориентированными экосистемами. gRPC входит
в фонд CNCF и напрямую поддерживается в большинстве современных
фреймворков и технологий. В частности, многие проекты, принадлежащие
к CNCF (например, Envoy; oreil.ly/vGQsj), используют gRPC в качестве

30   Глава 1. Введение в gRPC
коммуникационного протокола. Технология gRPC совместима со многими
инструментами, предоставляющими такие стандартные возможности,
как сбор метрик и мониторинг (например, для мониторинга gRPCприложений можно задействовать Prometheus; oreil.ly/AU3-7).
‰‰ Зрелость и широкая распространенность. Зрелость проекта gRPC обусловлена его активным использованием в Google и внедрением другими
крупными технологическими компаниями, такими как Square, Lyft, Netflix,
Docker, Cisco и CoreOS.
Как и любая другая технология, gRPC имеет не только преимущества, но
и недостатки. Понимание последних в процессе разработки приложений
может оказаться весьма полезным. Поэтому рассмотрим некоторые ограничения, характерные для gRPC.

Недостатки gRPC
Ниже перечислен ряд недостатков gRPC, которые необходимо учитывать
при выборе этой технологии для разработки своих приложений.
‰‰ gRPC может не подойти для сервисов, доступных снаружи. Если вы
хотите, чтобы ваши приложения или сервисы были доступны снаружи
по Интернету, то gRPC будет не самым удачным выбором, поскольку
большинство потребителей совсем недавно начали обращать внимание
на этот протокол, равно как и на REST/HTTP. Контрактно-ориентированная и сильно типизированная природа gRPC может сделать сервисы,
доступ к которым вы открываете, менее гибкими и ограничить контроль
со стороны потребителей (в отличие от таких протоколов, как GraphQL,
о которых мы поговорим в следующем подразделе). Для преодоления этой
проблемы был разработан шлюз gRPC, подробно описанный в главе 8.
‰‰ Кардинальные изменения в определении сервисов требуют больших усилий. Структурные изменения далеко не редкость при использовании
современных средств межсервисного взаимодействия. Если определение gRPC-сервиса меняется слишком сильно, то разработчику обычно
следует заново сгенерировать код как для клиента, так и для сервера.
Эту процедуру необходимо внедрить в существующий процесс непрерывной интеграции, что может усложнить цикл разработки в целом.
Тем не менее большинство изменений, вносимых в определение сервисов,
можно применять, не нарушая их контракты. gRPC сможет без какихлибо проблем взаимодействовать с клиентами и серверами, основанными

Эволюция межпроцессного взаимодействия   31

на другой версии proto-файлов, — главное, чтобы сохранялась обратная
совместимость. Таким образом, генерация кода в большинстве случаев
не требуется.
‰‰ Относительно небольшая экосистема. Экосистема gRPC все еще уступает
по своему охвату традиционному протоколу REST/HTTP. Поддержка
gRPC в браузерах и мобильных приложениях по-прежнему находится
в зачаточном состоянии.
Планируя разработку своих приложений, принимайте во внимание эти
ограничения. Естественно, gRPC подходит не для всех разновидностей межпроцессного взаимодействия. Вы должны проанализировать свои бизнес-требования и выбрать подходящий протокол обмена сообщениями. Некоторые
из рекомендаций о том, как сделать данный выбор, приводятся в главе 8.
Как уже обсуждалось в предыдущем разделе, в области межпроцессного
взаимодействия существует множество известных и новых технологий.
Вы должны четко представлять сильные и слабые стороны gRPC по сравнению с аналогичными инструментами, завоевавшими популярность в мире
разработки современных приложений; это поможет вам выбрать для своих
сервисов наиболее подходящий протокол.

Сравнение gRPC с другими протоколами: GraphQL и Thrift
Мы подробно обсудили некоторые ключевые ограничения REST, послужившие причиной создания gRPC. Но вместе с тем мы можем наблюдать
появление большого количества новых технологий IPC, пытающихся удовлетворить те же потребности. Ниже мы рассмотрим часть из них и сравним
их с gRPC.
Apache Thrift (thrift.apache.org) — RPC-фреймворк (изначально разработанный
компанией Facebook и затем переданный фонду Apache), похожий на gRPC.
Он использует собственный язык определения интерфейсов и поддерживает
большое количество языков программирования. Thrift позволяет описывать
типы данных и интерфейсы сервисов в файле определения. Компилятор
Thrift принимает этот файл как входные данные и генерирует из него код
для клиентской и серверной стороны. Транспортный слой Thrift предоставляет абстракции для сетевого ввода/вывода, что позволяет отделить ваши
определения от остальной системы. Это значит, что он умеет работать поверх
любого транспортного протокола, включая TCP, HTTP и т. д.

32   Глава 1. Введение в gRPC
Если сравнить Thrift с gRPC, то оба проекта имеют практически одинаковые
архитектурные принципы и сценарии использования. Однако между ними
есть несколько различий.
‰‰ Транспортный протокол. Разработчики gRPC пожертвовали универсальностью в пользу первоклассной поддержки HTTP/2. Применение
расширенных возможностей этого протокола позволило повысить эффективность и реализовать потоковый обмен сообщениями.
‰‰ Потоковая передача данных. Определения gRPC-сервисов имеют встроенную поддержку двунаправленной потоковой передачи данных (между
клиентом и сервером).
‰‰ Распространенность и сообщество. Что касается темпов внедрения, gRPC
демонстрирует довольно приличные показатели и обладает хорошей
экосистемой, построенной вокруг проектов фонда CNCF. К тому же у сообщества gRPC есть много хороших ресурсов, таких как документация,
сторонние презентации и практические примеры приложений, что делает
переход на эту технологию более гладким по сравнению с Thrift.
‰‰ Производительность. У нас нет официальных результатов сравнения
gRPC и Thrift, но в Интернете можно найти несколько сайтов, которые
оценивают скорость этих двух протоколов, и приводимые цифры говорят
о преимуществе Thrift. Тем не менее производительность gRPC тщательно
тестируется с выходом почти каждой новой версии (oreil.ly/Hy3mJ), так что
данный фактор вряд ли будет решающим при выборе между этими технологиями. Существуют и другие RPC-фреймворки с аналогичными возможностями, но gRPC на сегодняшний день — наиболее стандартизированное,
совместимое и широко распространенное решение в данной области.

GraphQL
GraphQL (graphql.org) — еще одна технология для межпроцессного взаимодействия (разработанная компанией Facebook и стандартизированная в качестве
открытого протокола), набирающая довольно серьезную популярность1.
Она представляет собой язык запросов для API и среду выполнения для
возвращения в ответ на эти запросы имеющихся данных. GraphQL подходит
к проблеме общения между клиентом и сервером совершенно по-другому,
позволяя клиентам самим определять, какие данные им нужны и в каком
1

См.: Бэнкс А., Порселло Е. GraphQL: язык запросов для современных веб-приложений. —
СПб.: Питер, 2019.

gRPC в реальных условиях   33

формате их следует вернуть. В свою очередь, gRPC предлагает фиксированный контракт для удаленных методов, на основе которого происходит
клиент-серверное взаимодействие.
GraphQL лучше подходит для сервисов и API, доступных непосредственно
внешним потребителям, особенно если клиенты хотят лучше контролировать,
какие именно данные должен возвращать сервер. Представьте, к примеру, что
в нашем гипотетическом интернет-магазине потребителям сервиса ProductInfo
требуется только часть информации о товарах, а не их полное описание, и при
этом им нужен способ выбора подходящих атрибутов. GraphQL позволяет
смоделировать сервис таким образом, чтобы потребители могли обращаться
к нему за необходимой информацией, используя одноименный язык запросов.
С прагматической точки зрения GraphQL лучше всего применять для сервисов и API, доступных снаружи, тогда как внутренние сервисы, стоящие за
этими интерфейсами, лучше реализовывать с помощью gRPC.
Теперь рассмотрим некоторые реальные примеры внедрения gRPC.

gRPC в реальных условиях
Успех любого протокола межпроцессного взаимодействия во многом зависит
от его общеотраслевого внедрения, а также от его пользователей и разработчиков. Протокол gRPC широко применяется для создания микросервисов
облачно-ориентированных приложений. Обсудим некоторые из ключевых
историй успеха gRPC.

Netflix
Netflix (https://www.netflix.com/by/) — компания, предоставляющая потоковое
видео по подписке. Она была одним из пионеров в практическом применении
микросервисной архитектуры в крупных масштабах. Потребители могут
просматривать видео через управляемые сервисы (или API), доступные
снаружи, а их работу обеспечивают сотни внутренних сервисов. В связи
с этим межпроцессное (или межсервисное) взаимодействие — один из самых
важных аспектов данной системы. На начальном этапе реализации микросервисов компания Netflix создала собственный технологический стек для
IPC на основе REST-сервисов, работающих поверх HTTP/1.1; это покрывало
почти 98 % всех сценариев использования данного продукта.

34   Глава 1. Введение в gRPC
Но со временем, когда этот подход начал применяться в масштабах Интернета, разработчики стали наблюдать некоторые его ограничения. Многие
потребители REST-микросервисов приходилось писать с нуля, анализируя
их ресурсы и форматы сообщений. Это занимало много времени, снижало
продуктивность разработчиков и повышало риск допущения ошибок в коде.
Реализация и потребление сервисов тоже затруднялись ввиду отсутствия
технологий для исчерпывающего определения их интерфейсов. Вначале,
чтобы обойти большинство из этих проблем, компания Netflix создала внутренний RPC-фреймворк, но после исследования уже готовых технологий
выбор пал на gRPC. В ходе исследования обнаружилось, что протокол gRPC
выгодно отличался с точки зрения простоты использования и предоставлял
все необходимые возможности.
Внедрение gRPC в Netflix привело к огромному скачку продуктивности
разработчиков. Например, сотни строчек самописного кода, которые раньше
требовались для каждого клиента, были заменены двумя-тремя строчками
в proto-файле. Благодаря gRPC на создание клиента уходит несколько минут, а не две или три недели, как раньше. Значительно улучшилась общая
стабильность платформы, поскольку программистам больше не нужно вручную реализовывать большинство потребительских возможностей; к тому же
теперь у них есть комплексный и безопасный способ определения интерфейсов сервисов. Благодаря повышению производительности снизилась общая
латентность платформы Netflix. Похоже, что с момента внедрения gRPC
в качестве основного средства межпроцессного взаимодействия некоторые
внутренние проекты компании (такие как Ribbon; oreil.ly/qKgv4), реализовывавшие IPC поверх REST и HTTP, прекратили активное развитие и теперь
просто поддерживаются в рабочем состоянии.

etcd
etcd (oreil.ly/wo4gM) — надежное распределенное хранилище типа «ключ —
значение», предназначенное для хранения наиболее важных данных в распределенных системах. Это один из самых популярных открытых проектов
фонда CNCF, который интегрирован во многие другие открытые инструменты, такие как Kubernetes. Один из ключевых факторов успеха протокола
gRPC состоит в том, что у него есть простой, четкий и легкий в применении
пользовательский API (oreil.ly/v-H-K). И благодаря этому интерфейсу etcd
может использовать все преимущества gRPC.

Резюме   35

Dropbox
Dropbox — сервис хранения файлов, который предоставляет облачное хранилище, синхронизацию данных, личное облако и клиентское ПО. Dropbox
состоит из сотен микросервисов, написанных на разных языках и обменивающихся миллионами запросов каждую секунду. Изначально в нем
использовалось несколько RPC-фреймворков, в том числе и самописный,
с нестандартными протоколами для ручной сериализации и десериализации, а также Apache Thrift и устаревшая RPC-система на основе протокола
HTTP/1.1 и сообщений в формате protobuf.
В итоге разработчики Dropbox полностью перешли на gRPC (что, помимо
прочего, позволило им использовать уже готовые форматы сообщений, описанные с помощью Protocol Buffers). Так на свет появился RPC-фреймворк
на основе gRPC под названием Courier (oreil.ly/msjcZ). Его целью было
не создание нового RPC-протокола, а интеграция gRPC с уже существу­
ющей в Dropbox инфраструктурой. Разработчики Dropbox расширили gRPC
с целью удовлетворить определенные требования, относящиеся к аутентификации, авторизации, обнаружению сервисов, сбору статистики, журналированию событий и средствам выполнения трассировки.
Эти истории успеха говорят о том, что gRPC — простой протокол межпроцессного взаимодействия, который повышает продуктивность и надежность,
позволяя создавать системы, работающие в масштабах Интернета. Это лишь
некоторые из наиболее известных первопроходцев в сфере внедрения
gRPC. Общее же количество проектов, использующих данный протокол,
постоянно растет.

Резюме
Современные распределенные приложения и сервисы редко находятся
в изоляции, и методы межпроцессного взаимодействия, которые их объединяют, — один из самых важных их аспектов. gRPC — масштабируемое и типобезопасное решение со слабой связанностью, позволяющее организовывать
IPC более эффективно, чем традиционные средства, основанные на REST/
HTTP. Благодаря этому возможно соединять, вызывать, администрировать
и отлаживать распределенные гетерогенные приложения в стиле, который
по своей простоте мало чем отличается от вызова локальных функций по
сетевым транспортным протоколам наподобие HTTP/2.

36   Глава 1. Введение в gRPC
gRPC также можно считать следующим этапом эволюции традиционного
подхода к RPC, лишенным прежних ограничений. Данный протокол широко
применяется различными интернет-гигантами в качестве средства для межпроцессного взаимодействия и чаще всего используется в целях налаживания
связи между внутренними сервисами.
Знания, полученные вами в этой главе, станут хорошим подспорьем для
изучения дальнейшего материала, посвященного различным аспектам
взаимодействия на основе gRPC. В следующей главе мы применим приобретенные навыки на практике и попытаемся создать с нуля настоящее
gRPC-приложение.

ГЛАВА 2

Начало работы
с gRPC
Хватит теории! Воспользуемся тем, что вы узнали в главе 1, для создания
с нуля настоящего gRPC-приложения. С помощью языков Go и Java мы напишем простой gRPC-сервис и клиент, который будет к нему обращаться.
В процессе разработки вы научитесь описывать gRPC-сервисы благодаря
Protocol Buffers, генерировать серверный каркас и клиентскую заглушку,
реализовывать бизнес-логику сервиса, запускать gRPC-сервер вместе с созданным вами сервисом и обращаться к последнему с помощью клиентского
приложения.
Воспользуемся примером интернет-магазина из главы 1 и попробуем создать
сервис, отвечающий за управление товарами. Он будет доступен удаленно,
и его потребители смогут добавлять в систему новые товары и запрашивать
информацию о них по их ID. Все описанное будет смоделировано с помощью
gRPC. Для реализации данного примера можно выбрать любой язык, но
в этой главе мы будем применять Go и Java.

В репозитории исходного кода для этой книги есть две реализации
данного примера: на Go и на Java.

На рис. 2.1 мы показали, как происходит клиент-серверное взаимодействие при вызове каждого метода из сервиса ProductInfo. На сервере выполняется gRPC-сервис, который предоставляет два удаленных метода:
addProduct(product) и getProduct(productId). Любой из них может быть
вызван клиентом.

38   Глава 2. Начало работы с gRPC

Рис. 2.1. Клиент-серверное взаимодействие сервиса ProductInfo

Начнем реализацию этого примера с определения gRPC-сервиса ProductInfo.

Определение сервиса
Как вы уже знаете из главы 1, первым делом при разработке gRPC-приложения
нужно определить интерфейс сервиса — то есть описать методы, которые
потребитель может вызывать удаленно, их параметры и форматы сообщений и т. д. Это делается с помощью языка определения интерфейсов (interface
definition language, IDL) Protocol Buffers (oreil.ly/1X5Ws), используемого
в gRPC.
В главе 3 мы более подробно обсудим методы определения сервисов,
которые применяются в разных стилях обмена сообщениями. Все, что
касается Protocol Buffers и реализации gRPC, будет рассмотрено в главе 4.

Определившись с бизнес-возможностями сервиса, вы можете описать интерфейс, который будет удовлетворять его требованиям. В нашем примере
сервис ProductInfo будет иметь два удаленных метода, addProduct(product)

Определение сервиса   39

и getProduct(productId), и два типа сообщений, Product и ProductID, которые
эти методы принимают и возвращают.
Дальше все это нужно описать в формате Protocol Buffers, пригодном для
определения как сервисов, так и типов сообщений. Сервис состоит из методов,
каждый из которых имеет тип и входящие/исходящие параметры. Сообщение
состоит из полей, а у каждого поля есть тип и уникальный индекс. Подробно
поговорим о том, как определить структуру сообщения.

Определение сообщений
Сообщение — структура данных, которая передается между клиентом и сервисом. Как видно на рис. 2.1, в примере с ProductInfo сообщения бывают двух
типов. Первый предназначен для описания товара (Product), используется
при добавлении новых товаров в систему и возвращении уже существующих.
Второй служит уникальным идентификатором (ProductID), применяется
при извлечении определенного товара из системы и возвращается, когда
этот товар добавляют впервые.
‰‰ ProductID — уникальный идентификатор товара, который может быть
строковым значением. Мы можем либо определить собственный тип
сообщений со строковым полем, либо использовать стандартный тип
google.protobuf.StringValue, доступный в библиотеке Protocol Buffers.
В этом примере мы выберем первый путь. Определение типа сообщений
ProductID показано в листинге 2.1.
Листинг 2.1. Определение типа сообщений ProductID в формате Protocol Buffers
message ProductID {
string value = 1;
}

‰‰ Product — наш собственный тип сообщений, который содержит описание
товара в нашем интернет-магазине. Может иметь набор полей, связанных
со свойствами товара. Мы будем исходить из того, что Product состоит
из следующих полей:
• ID — уникальный идентификатор товара;
• Name — название товара;
• Description — описание товара;
• Price — цена товара.

40   Глава 2. Начало работы с gRPC
Теперь мы можем определить наш тип сообщений с помощью Protocol Buffers,
как показано в листинге 2.2.
Листинг 2.2. Определение типа сообщений Product в формате Protocol Buffers
message Product {
string id = 1;
string name = 2;
string description = 3;
float price = 4;
}

Номера, присвоенные полям сообщений, используются в целях их идентификации. Поэтому мы не можем указать один и тот же номер для двух полей
в определении одного и того же сообщения. Более подробно о методиках
определения сообщений в Protocol Buffers и о том, почему у каждого поля
должен быть уникальный номер, мы поговорим в главе 4. А пока примите
это за правило.
Библиотека protobuf предоставляет набор стандартных типов сообщений. Мы можем использовать их в определении нашего сервиса, вместо
того чтобы создавать аналоги. Подробнее об этих типах можно почитать
в документации Protocol Buffers (oreil.ly/D8Ysn).

Итак, мы закончили с определением типов сообщений для сервиса ProductInfo.
Теперь можно перейти к определению его интерфейса.

Определение сервисов
Сервис — набор удаленных методов, доступных клиенту. В нашем примере у сервиса ProductInfo есть два таких метода: addProduct(product)
и getProduct(productId). Согласно правилам, которые действуют в Protocol
Buffers, каждый удаленный метод может иметь только один входящий параметр и возвращать лишь одно значение. Если нам нужно передать методу
несколько значений, то их необходимо сгруппировать в одном сообщении.
Именно так мы поступили с методом addProduct, для которого был определен
новый тип сообщений Product.

Определение сервиса   41

‰‰ addProduct создает в системе новый товар (Product). Принимает в качестве
входных данных сведения о товаре, и если он был успешно добавлен,
то возвращает его идентификационный номер. Определение метода
addProduct показано в листинге 2.3.
Листинг 2.3. Определение метода addProduct в формате Protocol Buffers
rpc addProduct(Product) returns (google.protobuf.StringValue);

‰‰ getProduct извлекает информацию о товаре. Принимает в качестве входных данных ProductID и возвращает Product, если такой товар присутствует
в системе. Определение метода getProduct показано в листинге 2.4.
Листинг 2.4. Определение метода getProduct в формате Protocol Buffers
rpc getProduct(google.protobuf.StringValue) returns (Product);

Если объединить все сообщения и сервисы, то получится полноценное
определение ProductInfo в формате Protocol Buffers, как показано в листинге 2.5.
Листинг 2.5. Определение gRPC-сервиса ProductInfo в формате Protocol Buffers
syntax = "proto3";
package ecommerce;




service ProductInfo { 
rpc addProduct(Product) returns (ProductID);
rpc getProduct(ProductID) returns (Product);
}




message Product { 
string id = 1; 
string name = 2;
string description = 3;
}
message ProductID { 
string value = 1;
}

 Определение сервиса начинается с указания версии Protocol Buffers
(proto3), которую мы используем.

42   Глава 2. Начало работы с gRPC
 Имена пакетов позволяют предотвратить конфликты имен между типами
сообщений и также служат для генерации кода.
 Определение интерфейса gRPC-сервиса.
 Удаленный метод для добавления товара, который возвращает ID этого
товара в качестве ответа.
 Удаленный метод для получения товара по его ID.
 Определение формата/типа сообщений Product.
 Поле (пара «имя — значение»), хранящее ID товара. Обладает уникальным
номером, с помощью которого его можно идентифицировать в двоичном
формате сообщений.
 Пользовательский тип для идентификационного номера товара.
В определении Protocol Buffers можно указать имя пакета (например,
ecommerce ), чтобы предотвратить конфликты имен в разных проектах.
Если указать этот пакет при генерации кода для наших сервисов и клиентов,
используя данное определение, то одноименные пакеты будут созданы для
соответствующих языков программирования (конечно, только при наличии
в самом языке такого понятия, как пакет). В имени пакета можно указать
номер версии — например, ecommerce.v1 или ecommerce.v2. Благодаря этому
будущие мажорные изменения, вносимые в API, смогут сосуществовать
в одной кодовой базе.
Распространенные IDE (integrated development environments — интегрированные среды разработки), такие как IntelliJ IDEA, Eclipse,
VSCode и др., содержат дополнения для работы с Protocol Buffers. Установив одно из таких дополнений в своей IDE, вы сможете с легкостью
создавать определения собственных сервисов в данном формате.

В этом контексте также следует упомянуть о процессе импорта из других
proto-файлов. Если нам нужно использовать уже существующие типы сообщений, то мы можем их импортировать. Например, чтобы применить
тип StringValue (google.protobuf.StringValue), определенный в файле wrap­
pers.proto, мы можем импортировать файл google/protobuf/wrappers.proto
в нашем определении:

Реализация   43

syntax = "proto3";
import "google/protobuf/wrappers.proto";
package ecommerce;
...

Закончив с определением, вы можете перейти к реализации своего gRPCсервиса и его клиента.

Реализация
Реализуем gRPC-сервис, состоящий из удаленных методов, которые мы
указали в его определении. gRPC-клиенты смогут обращаться к серверу
и вызывать эти методы.
Как показано на рис. 2.2, мы должны сначала скомпилировать определение
сервиса ProductInfo и сгенерировать исходный код на нужном нам языке.
gRPC имеет встроенную поддержку всех популярных языков, включая Java,
Go, Python, Ruby, C, C++, Node и т. д. Язык можно выбрать при реализации
сервиса или клиента. При этом серверная сторона вашего приложения может
быть написана на одном языке, а клиентская — на другом. В данном примере
мы реализуем клиент и сервер сразу на Go и на Java, чтобы вы могли выбрать
ту реализацию, которая вам нравится больше.

Рис. 2.2. Микросервис и потребитель на основе определения сервиса

44   Глава 2. Начало работы с gRPC
Сгенерировать исходный код из определения сервиса можно либо вручную,
скомпилировав proto-файл с помощью компилятора Protocol Buffers (oreil.ly/
CYEbY), либо путем использования средств автоматизации, таких как Bazel,
Maven или Gradle. Последние имеют набор готовых правил для генерации
кода во время сборки проекта, и их зачастую легче интегрировать в этот
процесс.
В этом примере мы будем использовать инструмент Gradle для сборки Javaприложения и дополнение к нему с целью сгенерировать из proto-файла код
для сервиса и клиента. Что касается приложения, написанного на Go, мы
задействуем компилятор Protocol Buffers.
Пошагово реализуем gRPC-сервер и клиент на Go и на Java. Но сначала
убедитесь в том, что в вашей локальной системе установлены как минимум
Java 7 и Go 1.11.

Разработка сервиса
Сгенерированный вами каркас сервиса содержит низкоуровневый код,
необходимый для установления gRPC-соединения, а также нужные вам
типы сообщений и интерфейсы. Создание сервиса заключается в реализации интерфейса, который был сгенерирован из его определения. Начнем
с Go и затем посмотрим, как этот же сервис можно реализовать на языке
Java.

Реализация gRPC-сервиса на Go
Реализация сервиса на Go состоит из трех шагов. Сначала нам нужно сгенерировать заглушки для определения сервиса, затем реализовать бизнес-логику
всех его удаленных методов, создать сервер, прослушивающий заданный
порт, и в конце зарегистрировать сервис для приема клиентских запросов.
Начнем с создания нового модуля Go, productinfo/service, который будет
хранить код сервиса. Внутри этого модуля будет находиться подкаталог
ecommerce с автоматически сгенерированным файлом заглушки. Создайте
внутри каталога productinfo подкаталог service. Войдите в него и выполните
следующую команду, чтобы создать модуль productinfo/service:
go mod init productinfo/service

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

Реализация   45

└─ productinfo
└─ service
├── go.mod
├ . . .
└── ecommerce
└── . . .

Нам также нужно указать в файле go.mod зависимости с определенными
версиями, как показано ниже:
module productinfo/service
require (
github.com/gofrs/uuid v3.2.0
github.com/golang/protobuf v1.3.2
github.com/google/uuid v1.1.1
google.golang.org/grpc v1.24.0
)

В Go 1.11 появилась новая концепция под названием «модули», которая позволяет разработчикам создавать и собирать проекты на Go за
пределами GOPATH. Чтобы создать модуль Go, нужно войти в каталог,
находящийся за пределами $GOPATH/src, и выполнить команду инициа­
лизации, указав имя модуля:
go mod init

После инициализации в корневом каталоге модуля появится файл
go.mod. Затем внутри этого модуля можно создать и собрать исходный
файл. Для поиска импортируемых файлов Go использует зависимости
с определенными версиями, перечисленные в файле go.mod.

Генерация заглушек для клиента и сервера. Теперь мы вручную сгенерируем заглушки для клиента и сервера, используя компилятор Protocol Buffers.
Для этого необходимо выполнить следующие действия.
‰‰ Загрузить и установить самый свежий компилятор Protocol Buffers версии 3 на странице выпусков GitHub (oreil.ly/Ez8qu).
Вы должны выбрать компилятор, который подходит для вашей платформы. Например, если вы используете компьютер с 64-битной версией
Linux, а версия компилятора Protocol Buffers имеет вид x.x.x, то вам нужно
загрузить файл protoc-x.x.x-linux-x86_64.zip.

46   Глава 2. Начало работы с gRPC
‰‰ Установите gRPC-библиотеку с помощью следующей команды:
go get -u google.golang.org/grpc

‰‰ Установите дополнение protoc для Go, выполнив такую команду:
go get -u github.com/golang/protobuf/protoc-gen-go

Подготовив все необходимое, мы можем сгенерировать код для определения
сервиса путем выполнения команды protoc, как показано ниже:
protoc -I ecommerce \ 
ecommerce/product_info.proto \ 
--go_out=plugins=grpc:/ecommerce



 Каталог, в котором хранится исходный proto-файл и его зависимости
(указывается с помощью параметра командной строки --proto_path или -I).
Если ничего не указать, то в качестве исходного будет использоваться текущий каталог. Внутри него должны находиться proto-файлы с зависимостями,
организованные в соответствии с именем пакета.
 Путь к proto-файлу, который вы хотите скомпилировать. Компилятор
прочитает этот файл и сгенерирует код на Go.
 Каталог, в котором будет сохранен сгенерированный код.
В результате выполнения команды в заданном подкаталоге модуля (ecom­
merce) будет сгенерирован файл заглушки ( product_info.pb.go). Теперь

на основе этого сгенерированного кода необходимо реализовать бизнеслогику.
Реализация бизнес-логики. Для начала создадим новый файл productinfo_
service.go внутри модуля Go (productinfo/service) и реализуем удаленные
методы, как показано в листинге 2.6.
Листинг 2.6. Реализация gRPC-сервиса ProductInfo на Go
package main
import (
"context"
"errors"
"log"

Реализация   47

"github.com/gofrs/uuid"
pb "productinfo/service/ecommerce"



)
// Сервер используется для реализации ecommerce/product_info
type server struct{ 
productMap map[string]*pb.Product
}
// AddProduct реализует ecommerce.AddProduct
func (s *server) AddProduct(ctx context.Context,
in *pb.Product) (*pb.ProductID, error) { 
out, err := uuid.NewV4()
if err != nil {
return nil, status.Errorf(codes.Internal,
"Error while generating Product ID", err)
}
in.Id = out.String()
if s.productMap == nil {
s.productMap = make(map[string]*pb.Product)
}
s.productMap[in.Id] = in
return &pb.ProductID{Value: in.Id}, status.New(codes.OK, "").Err()
}
// GetProduct реализует ecommerce.GetProduct
func (s *server) GetProduct(ctx context.Context, in *pb.ProductID)
(*pb.Product, error) { 
value, exists := s.productMap[in.Value]
if exists {
return value, status.New(codes.OK, "").Err()
}
return nil, status.Errorf(codes.NotFound, "Product does not exist.", in.Value)
}

Импортируем пакет со сгенерированным кодом, который мы только что
создали с помощью компилятора protobuf.
 Структура server является абстракцией сервера и позволяет подключать
к нему методы сервиса.
 Метод AddProduct принимает в качестве параметра Product и возвращает ProductID. Структуры Product и ProductID определены в файле

48   Глава 2. Начало работы с gRPC
product_info.pb.go, который был автоматически сгенерирован из определения
product_info.proto.

 Метод GetProduct принимает в качестве параметра ProductID и возвращает
Product.
 У обоих методов также есть параметр Context. Объект Context существует
на протяжении жизненного цикла запроса и содержит такие метаданные, как
идентификатор конечного пользователя, авторизационные токены и крайний
срок обработки запроса.
 Помимо итогового значения, оба метода могут возвращать ошибки (методы могут иметь несколько возвращаемых типов). Эти ошибки передаются
потребителям и могут быть обработаны на клиентской стороне.
Этого достаточно для реализации бизнес-логики сервиса ProductInfo. Теперь
мы можем создать простой сервер, который будет обслуживать наш сервис
и принимать запросы от клиента.
Создание сервера на Go. Чтобы получить сервер на языке Go, создадим
внутри того же пакета (productinfo/service) новый файл под названием
main.go и реализуем в нем метод main, как показано в листинге 2.7.
Листинг 2.7. Реализация gRPC-сервера на Go для обслуживания
сервиса ProductInfo
package main
import (
"log"
"net"
pb "productinfo/service/ecommerce"
"google.golang.org/grpc"



)
const (
port = ":50051"
)
func main() {
lis, err := net.Listen("tcp", port)



Реализация   49

if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer() 
pb.RegisterProductInfoServer(s, &server{})



log.Printf("Starting gRPC listener on port " + port)
if err := s.Serve(lis); err != nil { 
log.Fatalf("failed to serve: %v", err)
}
}

 Импортируем пакет со сгенерированным кодом, который мы только что
создали с помощью компилятора protobuf.
 TCP-прослушиватель, к которому мы хотим привязать gRPC-сервер,
создается на порте 50 051.
 Для создания нового экземпляра gRPC-сервера применяются API gRPC
на Go.
 Используя сгенерированные API, регистрируем реализованный ранее
сервис на только что созданном gRPC-сервере.
 Начинаем прослушивать входящие сообщения на порте 50 051.
Итак, мы завершили создание gRPC-сервиса на Go для нашего бизнессценария. Кроме того, мы создали простой сервер, который сделает методы
этого сервиса доступными снаружи и позволит принимать сообщения от
gRPC-клиентов.
То же самое можно сделать с помощью Java, если вы предпочитаете данный
язык. Весь процесс будет довольно схож с описанным выше. Посмотрим,
как это делается, на примере того же сервиса. Если вместо этого вы хотите
реализовать клиентское приложение на Go, то можете сразу переходить
к подразделу «Разработка gRPC-клиента» на с. 55.

Реализация gRPC-сервиса на Java
При создании gRPC-проекта на Java лучше всего использовать существующие средства сборки, такие как Gradle, Maven или Bazel, поскольку они
берут на себя управление всеми зависимостями, генерацию кода и т. д.

50   Глава 2. Начало работы с gRPC
Здесь мы будем применять Gradle, поэтому сначала посмотрим, как с помощью этого инструмента создать Java-проект, и затем уже перейдем к реализации бизнес-логики всех удаленных методов сервиса. В завершение мы
создадим сервер и зарегистрируем на нем свой сервис, чтобы принимать
клиентские запросы.
Gradle — инструмент, автоматизирующий процесс сборки. Он поддерживает множество языков и платформ, включая Java, Scala, Android,
C/C++ и Groovy, и тесно интегрирован с такими средствами разработки,
как Eclipse и IntelliJ IDEA. Инструкции по установке Gradle можно найти
на официальной странице проекта (gradle.org/install).

Подготовка Java-проекта. Сначала создадим Java-проект с помощью
Gradle (product-info-service). У вас должна получиться такая структура
каталогов:
product-info-service
├── build.gradle
├ . . .
└── src
├── main

├── java

└── resources
└── test
├── java
└── resources

Создайте внутри src/main каталог proto и поместите туда файл с определением
сервиса ProductInfo (файл .proto).
Теперь нужно обновить файл build.gradle. Добавьте в него зависимости
и дополнение protobuf для Gradle. Итоговый результат показан в лис­
тинге 2.8.
Листинг 2.8. Конфигурация Gradle для gRPC-проекта на Java
apply plugin: 'java'
apply plugin: 'com.google.protobuf'
repositories {
mavenCentral()
}

Реализация   51

def grpcVersion = '1.24.1'



dependencies { 
compile "io.grpc:grpc-netty:${grpcVersion}"
compile "io.grpc:grpc-protobuf:${grpcVersion}"
compile "io.grpc:grpc-stub:${grpcVersion}"
compile 'com.google.protobuf:protobuf-java:3.9.2'
}
buildscript {
repositories {
mavenCentral()
}
dependencies { 
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
}
}
protobuf { 
protoc {
artifact = 'com.google.protobuf:protoc:3.9.2'
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
sourceSets { 
main {
java {
srcDirs 'build/generated/source/proto/main/grpc'
srcDirs 'build/generated/source/proto/main/java'
}
}
}
jar { 
manifest {
attributes "Main-Class": "ecommerce.ProductInfoServer"
}

52   Глава 2. Начало работы с gRPC
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
apply plugin: 'application'
startScripts.enabled = false

 gRPC-библиотека для Java, которая используется в проекте Gradle.
 Внешние зависимости, необходимые нам в этом проекте.
 Дополнение protobuf для Gradle, которое мы применяем в этом проекте.
Укажите версию 0.7.5, если версия вашего пакета Gradle ниже 2.12.
 В настройках дополнения protobuf нужно указать версии компилятора
и исполняемого файла для Java.
 Это нужно для того, чтобы IDE, такие как IntelliJ IDEA, Eclipse или
NetBeans, знали о сгенерированном коде.
 Главный класс, который используется для запуска приложения.
Теперь выполним следующую команду, чтобы собрать библиотеку и сгенерировать код заглушки с помощью дополнения protobuf:
$ ./gradle build

Итак, мы подготовили проект на Java с автоматически сгенерированным
кодом. Теперь реализуем интерфейс сервиса и добавим бизнес-логику
в удаленные методы.
Реализация бизнес-логики. Для начала создадим в исходном каталоге
src/main/java Java-пакет ecommerce и поместим внутрь этого пакета новый
Java-класс ProductInfoImpl.java. Затем реализуем удаленные методы, как
показано в листинге 2.9.
Листинг 2.9. Реализация gRPC-сервиса ProductInfo на Java
package ecommerce;
import io.grpc.Status;
import io.grpc.StatusException;

Реализация   53

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class ProductInfoImpl extends ProductInfoGrpc.ProductInfoImplBase {



private Map productMap = new HashMap();
@Override
public void addProduct(
ProductInfoOuterClass.Product request,
io.grpc.stub.StreamObserver
responseObserver ) {
UUID uuid = UUID.randomUUID();
String randomUUIDString = uuid.toString();
productMap.put(randomUUIDString, request);
ProductInfoOuterClass.ProductID id =
ProductInfoOuterClass.ProductID.newBuilder()
.setValue(randomUUIDString).build();
responseObserver.onNext(id); 
responseObserver.onCompleted();
}



@Override
public void getProduct(
ProductInfoOuterClass.ProductID request,
io.grpc.stub.StreamObserver
responseObserver ) { 
String id = request.getValue();
if (productMap.containsKey(id)) {
responseObserver.onNext(
(ProductInfoOuterClass.Product) productMap.get(id)); 
responseObserver.onCompleted();
} else {
responseObserver.onError(new StatusException(Status.NOT_FOUND));
}
}



}

 Наследуем абстрактный класс ProductInfoGrpc.ProductInfoImplBase,
сгенерированный дополнением. Это позволит нам добавить бизнес-логику
в методы AddProduct и GetProduct, указанные в определении сервиса.
 Метод AddProduct принимает в качестве параметра Product ( Pro­
ductInfoOuterClass.Product ). Класс Product определен внутри класса
ProductInfoOuterClass, сгенерированного из определения сервиса.

54   Глава 2. Начало работы с gRPC
 Метод GetProduct принимает в качестве параметра ProductID (Pro­
ductInfoOuterClass.ProductID). Класс ProductID определен внутри класса
ProductInfoOuterClass, сгенерированного из определения сервиса.
 Объект responseObserver используется для возвращения ответа клиенту
и закрытия потока.
 Отправляем ответ клиенту.
 Закрываем поток, чтобы завершить клиентский вызов.
 Возвращаем клиенту ошибку.
Это все, что нужно для реализации на Java бизнес-логики сервиса ProductInfo.
Теперь мы можем создать простой сервер, который будет обслуживать наш
сервис и принимать запросы от клиента.
Создание сервера на Java. Чтобы сделать наш сервис ProductInfo доступным
снаружи, нам нужно зарегистрировать его на gRPC-сервере. Этот сервер
будет прослушивать указанный порт и передавать все запросы подходящему сервису. Создадим внутри пакета главный класс данного сервера,
ProductInfoServer.java, как показано в листинге 2.10.
Листинг 2.10. Реализация gRPC-сервера на Java для обслуживания
сервиса ProductInfo
package ecommerce;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
public class ProductInfoServer {
public static void main(String[] args)
throws IOException, InterruptedException {
int port = 50051;
Server server = ServerBuilder.forPort(port) 
.addService(new ProductInfoImpl())
.build()
.start();

Реализация   55

}

System.out.println("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> { 
System.err.println("Shutting down gRPC server since JVM is " +
"shutting down");
if (server != null) {
server.shutdown();
}
System.err.println("Server shut down");
}));
server.awaitTermination(); 

}

 Создаем экземпляр сервера на порте 50 051. Это порт, к которому мы
хотим привязать наш сервер и на котором он будет принимать входящие сообщения. Затем на сервер добавляется наша реализация сервиса
ProductInfo.
 Добавляем хук, чтобы gRPC-сервер останавливался вместе с JVM.
 Поток сервера продолжает выполняться, пока сервер не завершит
работу.
Итак, мы реализовали gRPC-сервис на обоих языках. Теперь можно переходить к реализации gRPC-клиента.

Разработка gRPC-клиента
Теперь, когда у нас есть реализация gRPC-сервиса, можно поговорить о создании приложения, которое будет взаимодействовать с ним. Для начала
сгенерируем клиентские заглушки из определения сервиса. На их основе
мы создадим простой gRPC-клиент, который станет подключаться к gRPCсерверу и вызывать предоставляемые им удаленные методы.
В данном примере мы напишем клиентские приложения на двух языках:
Go и Java. Стоит отметить: для написания клиента и сервера не обязательно
использовать один и тот же язык или платформу. gRPC работает в разных
языках и системах, так что вы можете выбрать поддерживаемые инструменты. Сначала обсудим реализацию на Go. Если вас интересует, как это
делается в языке Java, то можете пропустить следующий пункт и перейти
сразу к Java-клиенту.

56   Глава 2. Начало работы с gRPC
Реализация gRPC-клиента на Go
Сначала создадим новый модуль Go, productinfo/client , и подкаталог
ecommerce внутри него. Чтобы реализовать клиентское приложение, нам
также нужно сгенерировать заглушку, как мы делали при создании сервиса.
Для этого следует создать тот же файл, product_info.pb.go, и выполнить те же
шаги, поэтому здесь мы пропустим данный этап. Инструкции по генерации
файлов с заглушками можно найти в подпункте «Генерация заглушек для
клиента и сервера» на с. 45.
Теперь создадим внутри модуля productinfo/client файл под названием
productinfo_client.go и реализуем в нем главный метод, который будет
обращаться к удаленным методам сервиса, как показано в листинге 2.11.
Листинг 2.11. КлиентскоеgRPC-приложение на Go
package main
import (
"context"
"log"
"time"
pb "productinfo/client/ecommerce"
"google.golang.org/grpc"



)
const (
address = "localhost:50051"
)
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close() 
c := pb.NewProductInfoClient(conn) 



name := "Apple iPhone 11"
description := `Meet Apple iPhone 11. All-new dual-camera system with
Ultra Wide and Night mode.`
price := float32(1000.0)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()



Реализация   57

r, err := c.AddProduct(ctx,
&pb.Product{Name: name, Description: description, Price: price})
if err != nil {
log.Fatalf("Could not add product: %v", err)
}
log.Printf("Product ID: %s added successfully", r.Value)
product, err := c.GetProduct(ctx, &pb.ProductID{Value: r.Value})
if err != nil {
log.Fatalf("Could not get product: %v", err)
}
log.Printf("Product: ", product.String())





}

 Импортируем пакет со сгенерированным кодом, который мы создали
только что с помощью компилятора protobuf.
 Устанавливаем соединение с сервером с использованием предоставленного адреса ("localhost:50051"). В данном случае соединение между сервером
и клиентом будет незащищенным.
 Передаем соединение и создаем экземпляр заглушки, который содержит
все удаленные методы, доступные на сервере.
 Создаем объект Context, который будет передаваться вместе с удаленными
вызовами. Он содержит метаданные, такие как идентификатор конечного
пользователя, авторизационные токены и крайний срок обработки запроса.
Этот объект будет существовать до тех пор, пока запрос не будет обработан.
 Вызываем метод addProduct, передавая ему описание товара. В ответ
получим ID новой записи, если все пройдет успешно. В противном случае
будет возвращена ошибка.
 Вызываем метод getProduct, передавая ему ID товара. В ответ получим
описание товара, если все пройдет успешно. В противном случае будет возвращена ошибка.
 Закончив работу, закрываем соединение.
Итак, мы завершили разработку gRPC-клиента на языке Go. Теперь создадим аналогичный клиент на Java. Данный этап не является обязательным.
Если хотите узнать, как в Java создаются gRPC-клиенты, то можете продолжать; в противном случае можете сразу переходить к разделу «Сборка
и запуск» на с. 59.

58   Глава 2. Начало работы с gRPC
Реализация клиента на Java
Для создания клиентского приложения на Java нужно сначала подготовить
проект Gradle (product-info-client) и сгенерировать классы с помощью
специального дополнения, как мы делали при реализации сервиса. Пожалуйста, следуйте инструкциям, приведенным в подпункте «Подготовка
Java-проекта» на с. 50.
Сгенерировав код клиентской заглушки для нашего проекта с помощью
средства сборки Gradle, мы можем создать внутри пакета ecommerce новый
класс под названием ProductInfoClient и добавить в него бизнес-логику, как
показано в листинге 2.12.
Листинг 2.12. Клиентское gRPC-приложение на Java
package ecommerce;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.logging.Logger;
/**
* Пример gRPC-клиента для сервиса productInfo
*/
public class ProductInfoClient {
public static void main(String[] args) throws InterruptedException {
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 50051) 
.usePlaintext()
.build();
ProductInfoGrpc.ProductInfoBlockingStub stub =
ProductInfoGrpc.newBlockingStub(channel);



ProductInfoOuterClass.ProductID productID = stub.addProduct(
ProductInfoOuterClass.Product.newBuilder()
.setName("Apple iPhone 11")
.setDescription("Meet Apple iPhone 11. " +
All-new dual-camera system with " +
"Ultra Wide and Night mode.");
.setPrice(1000.0f)
.build());
System.out.println(productID.getValue());



Сборка и запуск   59

}

ProductInfoOuterClass.Product product = stub.getProduct(productID);
System.out.println(product.toString());
channel.shutdown(); 



}

 Создаем gRPC-канал, указывая адрес и порт сервера, к которому мы хотим
подключиться. Наш сервер находится на том же компьютере и прослушивает порт 50 051. Мы также включили передачу данных в открытом виде; это
значит, что соединение между клиентом и сервером будет незащищенным.
 Создаем клиентскую заглушку на основе только что созданного канала.
Заглушки бывают двух типов: BlockingStub и NonBlockingStub. Первые ждут
получения ответа от сервера, а вторые регистрируют для получения ответов
«объект — наблюдатель». Чтобы не усложнять наш пример, мы используем
BlockingStub.
 Вызываем метод addProduct, передавая ему описание товара. В ответ получим ID новой записи, если все пройдет успешно.
 Вызываем метод getProduct, передавая ему ID товара. В ответ получим
описание товара, если все пройдет успешно.
 Закончив работу, закрываем соединение, чтобы безопасно освободить
сетевые ресурсы, которые мы использовали в нашем приложении.
На этом разработка gRPC-клиента закончена. Теперь сделаем так, чтобы он
взаимодействовал с сервером.

Сборка и запуск
Пришло время собрать и запустить созданные нами клиентское и серверное gRPC-приложения. Для развертывания и запуска можно использовать
локальную систему, виртуальную машину, Docker или Kubernetes. В этом
разделе мы обсудим процесс сборки и запуска серверного и клиентского
gRPC-приложений на локальном компьютере.
Развертывание и запуск gRPC-приложений в средах Docker и Kubernetes
рассматриваются в главе 7.

60   Глава 2. Начало работы с gRPC
Запустим в локальной системе серверное и клиентское gRPC-приложения,
которые вы только что разработали. Поскольку они написаны на двух языках,
серверный код будет собираться отдельно.

Сборка сервера, написанного на Go
В результате реализации сервиса на языке Go итоговая структура каталогов
будет выглядеть так:
└─ productinfo
└─ service
├─ go.mod
├─ main.go
├─ productinfo_service.go
└─ ecommerce
└── product_info.pb.go

Теперь мы можем собрать наш сервис и сгенерировать двоичный файл (bin/
server). Для этого следует зайти в корневой каталог модуля Go (productinfo/
service) и выполнить в терминале такую команду:
$ go build -i -v -o bin/server

В случае успешного завершения сборки исполняемый файл (bin/server)
можно будет найти в каталоге bin.
Теперь займемся клиентом.

Сборка клиента, написанного на Go
Итоговая структура каталогов клиента, который мы реализовали на языке
Go, будет выглядеть так:
└─ productinfo
└─ client
├─ go.mod
├──main.go
└─ ecommerce
└── product_info.pb.go

Клиентский код можно собрать так же, как мы сделали с кодом сервиса, —
используя следующую команду:
$ go build -i -v -o bin/client

Сборка и запуск   61

В случае успешного завершения сборки исполняемый файл (bin/client)
можно будет найти в каталоге bin. Теперь запустим собранные нами файлы!

Запуск сервера и клиента, написанных на Go
Запустим клиент и сервер, которые мы собрали, в отдельных терминалах
и сделаем так, чтобы они общались друг с другом:
// запуск сервера
$ bin/server
2019/08/08 10:17:58 Starting gRPC listener on port :50051
// запуск клиента
$ bin/client
2019/08/08 11:20:01 Product ID: 5d0e7cdc-b9a0-11e9-93a4-6c96cfe0687d
added successfully
2019/08/08 11:20:01 Product: id:"5d0e7cdc-b9a0-11e9-93a4-6c96cfe0687d"
name:"Apple iPhone 11"
description:"Meet Apple iPhone 11. All-new dual-camera system with
Ultra Wide and Night mode."
price:1000

Теперь соберем сервер на языке Java.

Сборка сервера, написанного на Java
Поскольку наш Java-сервис реализован в виде проекта Gradle, мы можем
легко его собрать с помощью следующей команды:
$ gradle build

В результате успешной сборки в каталоге build/libs появится исполняемый
JAR-файл (server.jar).

Сборка клиента, написанного на Java
Как и в случае с сервисом, для сборки проекта достаточно одной команды:
$ gradle build

Если сборка пройдет успешно, то в каталоге build/libs можно будет найти исполняемый JAR-файл (client.jar).

62   Глава 2. Начало работы с gRPC

Запуск сервера и клиента, написанных на Java
Итак, мы собрали клиентский и серверный код, написанный на языке Java.
Теперь запустим его:
$ java -jar build/libs/server.jar
INFO: Server started, listening on 50051
$ java -jar build/libs/client.jar
INFO: Product ID: a143af20-12e6-483e-a28f-15a38b757ea8 added successfully.
INFO: Product: name: "Apple iPhone 11"
description: "Meet Apple iPhone 11. All-new dual-camera system with
Ultra Wide and Night mode."
price: 1000.0

Наш пример успешно запущен на локальном компьютере. Сначала клиентское приложение вызывает метод addProduct, передавая ему описание
товара, и получает в ответ идентификатор новой записи. Затем, с целью
получить описание только что созданного товара, он передает его идентификатор методу getProduct. Как упоминалось ранее в данной главе, клиент
и сервер могут быть написаны на разных языках. Например, если мы запустим Java-сервер и Go-клиент, то у нас не должно возникнуть никаких
проблем.
На этом данную главу можно считать завершенной.

Резюме
При разработке gRPC-приложения нужно сначала определить интерфейс
сервиса с помощью Protocol Buffers — расширяемого средства сериализации структурированных данных, не зависящего от языка и платформы.
Затем следует сгенерировать серверный и клиентский код для выбранного
языка программирования. В результате низкоуровневое взаимодействие
будет инкапсулировано, что упростит разработку серверной и клиентской
логики. На стороне сервера нужно реализовать методы, с вашего разрешения
доступные для удаленного вызова, и запустить gRPC-сервер, который станет
обслуживать ваш сервис. На клиентской стороне следует подключиться
к удаленному gRPC-серверу и вызвать удаленные методы, используя сгенерированный клиентский код.

Резюме   63

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

ГЛАВА 3

Методы взаимодействия
на основе gRPC
В первых двух главах вы познакомились с основами методов межпроцессного взаимодействия, которые предлагает gRPC, и получили практический
опыт создания простого gRPC-приложения. Вы уже знаете, как определить
и реализовать интерфейс сервиса, запустить gRPC-сервер и удаленно вызвать метод сервиса из gRPC-клиента. Взаимодействие клиента и сервера
проходит в простом стиле: на каждый запрос клиента приходит ровно один
ответ сервера. Тем не менее gRPC поддерживает и другие, более сложные
разновидности (стили) IPC.
В этой главе мы исследуем четыре фундаментальных метода взаимодействия,
которые применяются в gRPC-приложениях: простой (унарный) RPC, потоковый режим на стороне сервера, потоковый режим на стороне клиента
и двунаправленный потоковый режим. Мы продемонстрируем каждый из этих
подходов на реальных примерах, определим сервис с помощью языка IDL,
используемого в gRPC, и реализуем серверный и клиентский код на Go.
Примеры кода на Go и Java
Чтобы сохранить однородность наших примеров, все фрагменты кода,
представленные в текущей главе, написаны на Go. Но если вы программируете на Java, то полноценные примеры на этом языке для тех же сценари­
ев использования можно найти в репозитории кода для данной книги.

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

Простой (унарный) RPC   65

отправляет серверу один запрос и получает один ответ, содержащий информацию о состоянии и заключительные метаданные. На самом деле описанное
ничем не отличается от метода взаимодействия, с которым вы познакомились
в главах 1 и 2. Попробуем еще тщательнее разобраться в этом подходе, прибегнув к реальному сценарию использования.
Представьте, что нам нужно создать для нашего интернет-магазина, основанного на gRPC, сервис OrderManagement. В ходе его реализации нам предстоит
создать метод getOrder, который позволяет клиенту извлекать существующие
заказы по их идентификаторам. Как видно на рис. 3.1, клиент отправляет один
запрос с ID заказа, а сервер возвращает ему один ответ с информацией о данном
заказе. Это взаимодействие происходит по принципу простого RPC.

Рис. 3.1. Простой/унарный RPC

Теперь перейдем к реализации данного подхода. Первым делом нужно создать
определение сервиса OrderManagement с методом getOrder. Как показано в листинге 3.1, сервис можно определить с помощью Protocol Buffers; удаленный
метод getOrder принимает один запрос с ID заказа и возвращает один ответ,
содержащий сообщение Order. Последнее имеет структуру, необходимую
для представления заказа в этом конкретном случае.
Листинг 3.1. Определение сервиса OrderManagement с методом getOrder
по принципу простого RPC
syntax = "proto3";
import "google/protobuf/wrappers.proto";
package ecommerce;



66   Глава 3. Методы взаимодействия на основе gRPC
service OrderManagement {
rpc getOrder(google.protobuf.StringValue) returns (Order);
}
message Order { 
string id = 1;
repeated string items = 2;
string description = 3;
float price = 4;
string destination = 5;
}





 В этом пакете находятся стандартные типы, такие как StringValue.
 Удаленный метод для извлечения заказа.
 Определяем тип Order.
 Модификатор repeated используется для представления полей, которые
могут повторяться в сообщении сколько угодно (или вообще отсутствовать).
В нашем случае сообщение Order может содержать любое количество заказанных единиц товара.
Затем, применив proto-файл с определением gRPC-сервиса, можно сгенерировать каркас серверного кода и реализовать логику метода getOrder. Во фрагменте кода в листинге 3.2 показана реализация сервиса OrderManagement.
На вход в качестве запроса метод getOrder принимает один идентификатор
заказа (типа String); он ищет этот заказ на стороне сервера и возвращает его
в виде структуры типа Order. Сообщение Order может прийти вместе с nil
в качестве ошибки; так протокол gRPC будет знать, что мы закончили работу
с RPC и заказ можно вернуть клиенту.
Листинг 3.2. Реализация сервиса OrderManagement с методом getOrder на Go
// server/main.go
func (s *server) GetOrder(ctx context.Context,
orderId *wrapper.StringValue) (*pb.Order, error) {
// Реализация сервиса
ord := orderMap[orderId.Value]
return &ord, nil
}

Теперь реализуем клиентскую логику, чтобы удаленно вызывать метод
getOrder. Как и в случае с серверной стороной, вы можете сгенерировать
код для нужного вам языка и затем использовать полученную заглушку
для обращения к сервису. В листинге 3.3 наш gRPC-клиент на языке Go об-

Потоковый RPC на стороне сервера   67

ращается к сервису OrderManagement. В первую очередь, конечно же, следует
установить соединение с сервером и вызвать удаленный метод, задействовав
метод клиентской заглушки getOrder. В качестве ответа вы получите сообщение Order с информацией о заказе, которое описано в определении нашего
сервиса с помощью Protocol Buffers.
Низкоуровневые аспекты обмена сообщениями по gRPC между сервером
и клиентом объясняются в главе 4. Как можно видеть в представленном
выше определении, помимо наших параметров метод getOrder принимает
объект Context, который передавался в предыдущей реализации сервиса
OrderManagement. Данный объект содержит элементы, применяемые для
управления поведением gRPC, — например, крайние сроки и механизм
отмены запроса. Подробнее об этом читайте в главе 5.
Листинг 3.3. Реализация клиента на Go, которая вызывает удаленный метод getOrder
// установление соединения с сервером
...
orderMgtClient := pb.NewOrderManagementClient(conn)
...
// получаем заказ
retrievedOrder , err := orderMgtClient.GetOrder(ctx,
&wrapper.StringValue{Value: "106"})
log.Print("GetOrder Response -> : ", retrievedOrder)

Метод простого RPC довольно прост в реализации и хорошо подходит для
большинства сценариев использования межпроцессного взаимодействия.
Его реализация мало чем отличается в разных языках программирования.
Исходный код для Go и Java можно найти в репозитории к этой книге.
Теперь перейдем к потоковому RPC на стороне сервера.

Потоковый RPC на стороне сервера
При взаимодействии сервера и клиента по принципу простого RPC каждый запрос инициирует отдельный ответ. Что касается потокового RPC на
стороне сервера, один клиентский запрос генерирует цепочку из нескольких ответов. Эту цепочку называют потоком. После возвращения своего
последнего ответа сервер передает клиенту заключительные метаданные
с информацией о своем состоянии.

68   Глава 3. Методы взаимодействия на основе gRPC
Чтобы лучше понять, как это работает, рассмотрим реальный пример.
Допустим, наш сервис OrderManagement должен предоставлять возможность
поиска по заказам: мы отправляем поисковый запрос и получаем найденные
результаты (рис. 3.2). Сервис OrderManagement может возвращать подходящие
заказы не все сразу, а по мере их обнаружения. Это значит, что в ответ на
один запрос клиент получит несколько сообщений.

Рис. 3.2. Потоковый RPC на стороне сервера

Теперь добавим в определение нашего gRPC-сервиса OrderManagement метод
searchOrder. Как показано в листинге 3.4, определение этого метода довольно похоже на то, которое применялось в случае с простым RPC, но теперь
мы возвращаем заказы в виде потока, используя в proto-файле выражение
returns (stream Order).
Листинг 3.4. Определение сервиса с потоковым RPC на стороне сервера
syntax = "proto3";
import "google/protobuf/wrappers.proto";
package ecommerce;
service OrderManagement {
...
rpc searchOrders(google.protobuf.StringValue) returns (stream Order);
...
}
message Order {
string id = 1;



Потоковый RPC на стороне сервера   69

repeated string items = 2;
string description = 3;
float price = 4;
string destination = 5;
}

 Включаем потоковую передачу, возвращая поток сообщений типа Order.
Из определения gRPC-сервиса OrderManagement можно сгенерировать серверный код и реализовать полученные интерфейсы, чтобы наполнить логикой
метод searchOrder. В реализации на языке Go, представленной в листинге 3.5,
метод SearchOrders принимает два параметра: строковое значение searchQuery
и специальный объект OrderManagement_SearchOrdersServer, в который мы
будем записывать наши ответы. Этот объект представляет собой ссылку
на поток, через который будут возвращаться результаты. Задача бизнеслогики — найти подходящие заказы и отправить их один за другим в виде
потока. Каждый обнаруженный заказ записывается в поток с помощью
метода Send(…), принадлежащего упомянутому выше объекту. После записи последнего ответа вы можете сообщить и о завершении потока, вернув
клиенту nil, состояние сервера и заключительные метаданные.
Листинг 3.5. Реализация сервиса OrderManagement с методом searchOrders на Go
func (s *server) SearchOrders(searchQuery *wrappers.StringValue,
stream pb.OrderManagement_SearchOrdersServer) error {
for key, order := range orderMap {
log.Print(key, order)
for _, itemStr := range order.Items {
log.Print(itemStr)
if strings.Contains(
itemStr, searchQuery.Value) { 
// Отправляем подходящие заказы в поток
err := stream.Send(&order) 
if err != nil {
return fmt.Errorf(
"error sending message to stream : %v", err)
}
log.Print("Matching Order Found : " + key)
break
}
}
}
return nil
}



70   Глава 3. Методы взаимодействия на основе gRPC
 Находим подходящие заказы.
 Отправляем найденный заказ в поток.
 Проверяем, не возникли ли какие-либо ошибки при потоковой передаче
сообщений клиенту.
Удаленный вызов методов на клиентской стороне сильно напоминает простой
RPC. Но в данном случае необходимо обрабатывать множественные ответы,
поскольку сервер записывает в поток цепочку ответов. Таким образом,
в реализации gRPC-клиента на Go (листинг 3.6) мы последовательно извлекаем сообщения из клиентского потока, используя метод Recv(), и делаем
это до тех пор, пока данный поток не закончится.
Листинг 3.6. Реализация клиента OrderManagement с методом searchOrders на Go
// устанавливаем соединение с сервером
...
c := pb.NewOrderManagementClient(conn)
...
searchStream, _ := c.SearchOrders(ctx,
&wrapper.StringValue{Value: "Google"})



for {

searchOrder, err := searchStream.Recv() 
if err == io.EOF { 
break
}
// обрабатываем другие потенциальные ошибки
log.Print("Search Result : ", searchOrder)

}

 Функция SearchOrders возвращает клиентский поток OrderManagement_
SearchOrdersClient, у которого есть метод Recv.
 Вызываем метод Recv() из клиентского потока для последовательного
получения ответов типа Order.
 При обнаружении конца потока Recv возвращает io.EOF.
Теперь рассмотрим потоковый RPC на стороне клиента, который факти­
чески является противоположностью подхода, описанного в текущем
разделе.

Потоковый RPC на стороне клиента   71

Потоковый RPC на стороне клиента
При использовании этого метода клиент отправляет серверу не одно,
а несколько сообщений, а тот возвращает один ответ. Но, чтобы ответить,
серверу вовсе не обязательно ждать получения всех клиентских запросов.
Ответ можно отправить после извлечения из потока одного, нескольких
или всех сообщений.
Чтобы понять, как это работает, расширим наш сервис OrderManagement.
Допустим, вам нужно добавить новый метод для обновления нескольких
заказов, updateOrders (рис. 3.3). Мы будем передавать список заказов в виде
потока сообщений, а сервер станет их обрабатывать и возвращать ответ с состоянием заказов, которые были обновлены.

Рис. 3.3. Потоковый RPC на стороне клиента

Добавим метод updateOrders в определение сервиса OrderManagement, как показано в листинге 3.7. В качестве параметра можно использовать stream order;
благодаря этому метод updateOrders будет знать, что клиент передает ему
множественные сообщения. Поскольку сервер отправляет обратно только
один ответ, возвращаемое значение будет иметь обычный тип StringValue.
Листинг 3.7. Определение сервиса с потоковым RPC на стороне клиента
syntax = "proto3";
import "google/protobuf/wrappers.proto";
package ecommerce;

72   Глава 3. Методы взаимодействия на основе gRPC
service OrderManagement {
...
rpc updateOrders(stream Order) returns (google.protobuf.StringValue);
...
}
message Order {
string id = 1;
repeated string items = 2;
string description = 3;
float price = 4;
string destination = 5;
}

Обновив определение сервиса OrderManagement, мы можем сгенерировать
серверный и клиентский код. На серверной стороне нужно реализовать
интерфейс сгенерированного метода UpdateOrders. В реализации на языке Go, представленной в листинге 3.8, UpdateOrders принимает объект
OrderManagement_UpdateOrdersServer, который ссылается на поток сообщений, отправляемых клиентом. Таким образом, сообщения можно читать
с помощью метода Recv().
Количество сообщений, которые вам нужно прочитать, зависит от бизнес-логики. Чтобы вернуть ответ, сервису достаточно вызвать из объекта
OrderManagement_UpdateOrdersServer метод SendAndClose; это будет сигнализировать об окончании потока серверных сообщений. Если сервер решит
прекратить чтение из клиентского потока, не доходя до его конца, то должен
отменить данный поток; таким образом, клиент будет знать, что ему больше
не нужно отправлять свои сообщения.
Листинг 3.8. Реализация сервиса OrderManagement с методом updateOrders на Go
func (s *server) UpdateOrders(stream pb.OrderManagement_UpdateOrdersServer)
error {
ordersStr := "Updated Order IDs : "
for {
order, err := stream.Recv() 
if err == io.EOF { 
// завершаем чтение потока заказов
return stream.SendAndClose(
&wrapper.StringValue{Value: "Orders processed "
+ ordersStr})
}

Потоковый RPC на стороне клиента   73

// обновляем заказ
orderMap[order.Id] = *order
log.Printf("Order ID ", order.Id, ": Updated")
ordersStr += order.Id + ", "
}
}

 Читаем сообщение из клиентского потока.
 Проверяем, не закончился ли поток.
Теперь взглянем на клиентскую реализацию этого подхода. Как видно в листинге 3.9, клиент может отправлять множественные сообщения, используя
ссылку на клиентский поток и метод updateStream.Send. Закончив передачу сообщений, клиент может просигнализировать о завершении потока и получить
ответ от сервиса. Для этого у ссылки на поток предусмотрен метод CloseAndRecv.
Листинг 3.9. Реализация клиента OrderManagement с методом updateOrders на Go
// устанавливаем соединение с сервером
...
c := pb.NewOrderManagementClient(conn)
...
updateStream, err := client.UpdateOrders(ctx)



if err != nil { 
log.Fatalf("%v.UpdateOrders(_) = _, %v", client, err)
}
// обновляем заказ 1
if err := updateStream.Send(&updOrder1); err != nil {
log.Fatalf("%v.Send(%v) = %v",
updateStream, updOrder1, err) 
}
// обновляем заказ 2
if err := updateStream.Send(&updOrder2); err != nil {
log.Fatalf("%v.Send(%v) = %v",
updateStream, updOrder2, err)
}
// обновляем заказ 3
if err := updateStream.Send(&updOrder3); err != nil {
log.Fatalf("%v.Send(%v) = %v",
updateStream, updOrder3, err)
}



74   Глава 3. Методы взаимодействия на основе gRPC

updateRes, err := updateStream.CloseAndRecv() 
if err != nil {
log.Fatalf("%v.CloseAndRecv() got error %v, want %v",
updateStream, err, nil)
}
log.Printf("Update Orders Res : %s", updateRes)

 Вызов удаленного метода UpdateOrders.
 Обработка ошибок, связанных с UpdateOrders.
 Отправка обновленных заказов в клиентский поток.
 Обработка ошибок, связанных с отправкой сообщений в поток.
 Закрытие потока и получение ответа.
В результате вызова этой функции вы получите от сервиса ответное сообщение. Вы уже должны хорошо ориентироваться в потоковом RPC как
на стороне сервера, так и на стороне клиента, поэтому перейдем к двунаправленному потоковому режиму, который в некотором роде сочетает оба
описанных способа.

Двунаправленный потоковый RPC
В двунаправленном потоковом режиме запрос клиента и ответ сервера
представлены в виде потоков сообщений. Вызов должен быть инициирован на клиентской стороне, но дальнейшее взаимодействие зависит лишь
от логики клиентского и серверного кода. Чтобы получше разобраться, как
это работает, рассмотрим пример. Допустим, нам нужно добавить в наш
сервис OrderManagement возможность обрабатывать заказы, как показано на
рис. 3.4: клиент отправляет непрерывный поток заказов, а сервер объединяет
их в партии, в зависимости от адреса доставки.
Этот бизнес-сценарий можно разделить на следующие ключевые этапы.
‰‰ Процесс инициируется клиентским приложением, которое устанавливает
соединение с сервером и передает ему метаданные (заголовки) вызова.
‰‰ Успешно установив соединение, клиентское приложение начинает передачу непрерывного потока идентификаторов заказов, которые должен
обработать сервис OrderManagement.

Двунаправленный потоковый RPC   75

Рис. 3.4. Двунаправленный потоковый RPC

‰‰ Каждый ID передается серверу в виде отдельного gRPC-сообщения.
‰‰ Сервер обрабатывает все заказы, соответствующие полученным идентификаторам, и объединяет их в партии на основе адреса доставки.
‰‰ Партия может содержать несколько заказов, которые должны быть доставлены в одно место назначения.
‰‰ Заказы обрабатываются партиями. Когда партия достигает предельного
размера, все заказы в текущей партии возвращаются клиенту.
‰‰ Например, упорядоченный поток из четырех заказов, два из которых отправляются по адресу X и еще два — по адресу Y, можно обозначить как
X, Y, X, Y. Если размер партии равен 3, то заказы следует сгруппировать
так: [X, X], [Y] и [Y]. После группирования они возвращаются клиенту
в виде потока.
Ключевая идея данного бизнес-сценария состоит в том, что после вызова
RPC-метода клиент и сервис могут свободно обмениваться сообщениями
(при этом каждая из сторон может завершить поток в любой момент).
Теперь посмотрим, как в этом случае будет выглядеть определение сервиса.
В листинге 3.10 показан метод processOrders, который принимает в качестве параметра поток строк (представляющий идентификаторы заказов)
и возвращает поток объектов CombinedShipment. При объявлении входящих
и исходящих параметров метода используется модификатор stream, что
позволяет нам определить двунаправленное потоковое взаимодействие.
В определении сервиса также объявляется сообщение со списком сгруппированных заказов.

76   Глава 3. Методы взаимодействия на основе gRPC
Листинг 3.10. Определение сервиса для двунаправленного
потокового RPC
syntax = "proto3";
import "google/protobuf/wrappers.proto";
package ecommerce;
service OrderManagement {
...
rpc processOrders(stream google.protobuf.StringValue)
returns (stream CombinedShipment); 
}
message Order { 
string id = 1;
repeated string items = 2;
string description = 3;
float price = 4;
string destination = 5;
}
message CombinedShipment { 
string id = 1;
string status = 2;
repeated Order ordersList = 3;
}

 В двунаправленном RPC входящие и исходящие параметры объявляются
в качестве потоков.
 Структура сообщения Order.
 Структура сообщения CombinedShipment.
Теперь из этого обновленного определения сервиса можно сгенерировать серверный код. Сервис OrderManagement должен реализовывать метод
processOrders. В реализации на языке Go, представленной в листинге 3.11,
этот метод принимает объект OrderManagement_ProcessOrdersServer, который
ссылается на поток сообщений между клиентом и сервисом. С помощью этого
объекта сервис может читать из потока клиентские запросы и записывать
в него свои ответы. В методе processOrders сервис одновременно читает из
потока входящие сообщения с помощью метода Recv() и выполняет запись
в тот же поток, используя метод Send().

Двунаправленный потоковый RPC   77

В целях упрощения в листинге 3.10 опущена часть кода. Полный ис­
ходный текст данного примера находится в репозитории кода этой
книги.

Листинг 3.11. Реализация сервиса OrderManagement
с методом processOrders на Go
func (s *server) ProcessOrders(
stream pb.OrderManagement_ProcessOrdersServer) error {
...
for {
orderId, err := stream.Recv() 
if err == io.EOF {

...
for _, comb := range combinedShipmentMap {
stream.Send(&comb) 
}
return nil

}
if err != nil {
return err
}
// логика для объединения заказов в партии
// на основе адреса доставки
...
//
if batchMarker == orderBatchSize { 
// передаем клиенту поток заказов, объединенных в партии
for _, comb := range combinedShipmentMap {
// передаем клиенту партию объединенных заказов
stream.Send(&comb)

}
batchMarker = 0
combinedShipmentMap = make(
map[string]pb.CombinedShipment)
} else {
batchMarker++
}
}
}

 Читаем ID заказов из входящего потока.
 Продолжаем читать, пока не обнаружим конец потока.

78   Глава 3. Методы взаимодействия на основе gRPC
 При обнаружении конца потока отправляем клиенту все сгруппированные
заказы, которые еще остались.
 Сервер завершает поток, возвращая nil.
 Заказы обрабатываются группами. Когда достигается предельный размер партии, все объединенные заказы возвращаются клиенту в виде потока.
 Запись объединенных заказов в поток.
Мы обрабатываем входящие заказы на основе их идентификаторов, и в момент создания новой партии сервис записывает ее в тот же поток (в отличие
от потокового RPC на стороне клиента, когда поток закрывается после записи с помощью метода SendAndClose). При обнаружении конца клиентского
потока сервер закрывает этот поток со своей стороны, возвращая nil.
Клиентская реализация (листинг 3.12) тоже имеет много общего с предыдущими примерами. При вызове метода processOrders из объекта OrderMana­
gement клиент получает ссылку на поток (streamProcOrder ), с помощью
которой он отправляет свои сообщения и читает ответы, возвращаемые
сервером.
Листинг 3.12. Реализация клиента OrderManagement
с методом processOrders на Go
// обработка заказов
streamProcOrder, _ := c.ProcessOrders(ctx) 
if err := streamProcOrder.Send(
&wrapper.StringValue{Value:"102"}); err != nil { 
log.Fatalf("%v.Send(%v) = %v", client, "102", err)
}
if err := streamProcOrder.Send(
&wrapper.StringValue{Value:"103"}); err != nil {
log.Fatalf("%v.Send(%v) = %v", client, "103", err)
}
if err := streamProcOrder.Send(
&wrapper.StringValue{Value:"104"}); err != nil {
log.Fatalf("%v.Send(%v) = %v", client, "104", err)
}
channel := make(chan struct{}) 
go asncClientBidirectionalRPC(streamProcOrder, channel)
time.Sleep(time.Millisecond * 1000) 



Двунаправленный потоковый RPC   79

if err := streamProcOrder.Send(
&wrapper.StringValue{Value:"101"}); err != nil {
log.Fatalf("%v.Send(%v) = %v", client, "101", err)
}
if err := streamProcOrder.CloseSend(); err != nil {
log.Fatal(err)



}