От интерфейса к серверу: использование gRPC (бонус: как использовать gRPC с хуками React!)

Что такое gRPC?

Если вы похожи на меня, вы, возможно, даже никогда не слышали о gRPC, не говоря уже о том, чтобы знать, каковы некоторые из его преимуществ и недостатков. gRPC — это рекурсивная аббревиатура, обозначающая удаленный вызов процедур gRPC. Это комбинация протокола HTTP 2.0 и буферов протокола, также известных как протобуферы.

Для чего нужен gRPC?

Основное преимущество gRPC заключается в том, что это универсальный коммуникационный протокол, поскольку протобуферы закодированы в двоичный код (проще не бывает!). Таким образом, он идеально подходит для распределенных вычислений и обмена сообщениями между системами, которые написаны на разных языках или платформах, но, тем не менее, должны взаимодействовать. Хотя для этой межсистемной связи можно использовать что-то вроде протокола HTTP, это сопряжено с накладными расходами, поскольку каждая система также должна иметь сервер, предоставляющий какую-то службу REST или REST-подобную службу с допустимыми конечными точками и так далее. Protobufs, будучи на некоторых уровнях даже более строгими, чем REST через HTTP, могут быть в коде столь же простыми, как однострочный код.

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

Недостатки gRPC

Как и все в технологиях, ничто не является серебряной пулей, и gRPC не является исключением, даже с его впечатляющим списком преимуществ. По состоянию на 13 июня 2022 г., когда был опубликован этот пост, у gRPC были основные недостатки (все проблемы на стороне клиента, особенно в Интернете):

  1. Поддержка TypeScript остается экспериментальной функцией gRPC.
  2. Для связи веб-клиентов с сервером, на котором работает gRPC, требуется прокси, и для этого прокси есть только два варианта: а. Невероятный клиент gRPC-Web или б. Клиент Google gRPC-Web
  3. Кроме того, в настоящее время ни одна из реализаций полностью не соответствует полной спецификации gRPC.
  4. Поддержка WebSocket в качестве транспортного уровня доступна только в клиенте Improbable gRPC-Web и остается экспериментальной, не рекомендуемой для использования в производстве.

(Кстати, если вы раздумываете, что выбрать, на данный момент только Невероятный клиент gRPC-Web поддерживает двунаправленную потоковую передачу, что ближе к полной спецификации gRPC.)

Начать

Бэкэнд-реализация

Во-первых, я начал с рекомендованного gRPC начального репозитория для изучения gRPC, их helloworld пример, который является частью официального репозитория gRPC.

Используя этот код, я добавил в Go новый служебный метод под названием Reverse:

package utils

// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
 r := []rune(s)
 for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
  r[i], r[j] = r[j], r[i]
 }
 return string(r)
}

И изменил исходную реализацию функции SayHello, чтобы использовать эту функцию Reverse:

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
 log.Printf("Received: %v", in.GetName())
 log.Printf("Returning: %v", utils.Reverse(in.GetName()))
 return &pb.HelloReply{Message: utils.Reverse(in.GetName())}, nil
}

Реализация внешнего интерфейса

Я создал приложение React с помощью create-react-app, удалил все лишнее (стили, изображения и т. д.) и добавил некоторый текст описания с помощью <textarea>.

Теперь интересная часть: я решил использовать пользовательский React Hook для управления состоянием сообщения, ответа, и взаимодействия с вызовом gRPC. Ловушка прослушивает значение сообщения, которое предоставляет пользователь, и всякий раз, когда оно изменяется, вызывается служба реверса метода gRPC. Это довольно кратко выглядит как хук, который я назвал useGreeterService:

import { useEffect, useState } from "react";
import { sayHello } from "../services/greeterService";

export const useGreeterService = (): [
  string,
  React.Dispatch<React.SetStateAction<string>>,
  string
] => {
  const [message, setMessage] = useState("");
  const [responseMessage, setResponseMessage] = useState("");
  useEffect(() => {
    sayHello(message, setResponseMessage);
  }, [message]);

  return [message, setMessage, responseMessage];
};

Обратите внимание на неуловимую мощь этого вызова sayHello — однострочника! Никакого fetch, никакой настройки заголовков, файлов cookie, HTTP-методов или создания какой-либо пользовательской службы API — gRPC абстрагирует все это для нас.

Использование useGreeterService в App.tsx выглядит следующим образом:

import { useState } from "react"
import { useGreeterService } from "./hooks/useGreeterService";

const App = () => {
  const [message, setMessage, responseMessage] = useGreeterService();
  
  return (
    <>
    <h1>Simple message reverser over gRPC</h1>
    <p>(Don't ask me why you would actually want to reverse a string server-side, this is just for fun!</p>
    <label title="Type a message in realtime over the gRPC wire">Type a message in realtime over the gRPC wire:</label>
    <br/>
    <textarea value={message} onChange={(event) => setMessage(event.target.value)}/>
    <p>And see the response message here: {responseMessage}</p>
    </>
  );
}

export default App;

Веб-прокси gRPC

После того, как ваш серверный и внешний интерфейс запущены, остается добавить еще один элемент: прокси-сервер gRPC. К счастью, у нас есть прокси Improbable, готовый к использованию. Сначала установите его с помощью:

GOPATH=~/go ; export GOPATH
git clone https://github.com/improbable-eng/grpc-web.git $GOPATH/src/github.com/improbable-eng/grpc-web
cd $GOPATH/src/github.com/improbable-eng/grpc-web
dep ensure # after installing dep
go install ./go/grpcwebproxy # installs into $GOPATH/bin/grpcwebproxy

Затем запустите его с помощью:

GOPATH=~/go ; export GOPATH
$GOPATH/bin/grpcwebproxy \
    --backend_addr=localhost:50051 \
    --run_tls_server=false \
    --use_websockets \
    --allow_all_origins

*Примечание: в моем первоначальном эксперименте я хотел использовать WebSockets в качестве транспортного уровня для оптимальной производительности. Тем не менее, после дальнейшего изучения gRPC (и, как упоминалось выше), использование WebSockets в качестве транспортного уровня технически все еще является экспериментальной функцией, поэтому я бы рекомендовал удалить флаг --use_websockets из приведенной выше команды при запуске веб-прокси в рабочей среде.

(Хотя мне не совсем понятно, что является экспериментальным в использовании WebSockets в качестве транспортного уровня. В этом простом примере у меня не возникло никаких проблем!)

Пример кода

Код этого небольшого эксперимента можно найти на GitHub. README может дать вам еще более подробную информацию о том, как все это настроить и запустить.

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

Спасибо!

Если вы хотели погрузиться в мир gRPC, я надеюсь, что этот пост убедил вас попробовать и показал, как легко приступить к работе с вызовом gRPC полного стека!

Ваше здоровье! 🍻

-Крис

У меня есть задача научить 1 000 000 многообещающих разработчиков работать с реальным программным обеспечением! Подробнее читайте в моем блоге:

https://chrisfrew.in/

и мой профиль и курсы Udemy:

https://www.udemy.com/user/chris-frewin/