Реализуйте приложение реального времени с помощью подписок GraphQL и Go

В этой статье я расскажу, как реализовать подписки GraphQL с Go.

Зависимости

Обязательные зависимости следующие:

Сторона сервера

Сторона внешнего интерфейса

Подписки GraphQL

Подписки GraphQL позволяют периодически получать данные без опроса клиентов. Подписки поддерживают активное соединение через WebSockets и позволяют получать уведомления сразу после обнаружения обновлений.

Redis Stream

В версии 2 Redis предоставил функцию Pub / Sub, которая может разделять издателей и подписчиков. Используя это, издатели могут отправлять сообщения любому количеству подписчиков на канале, не зная, какие подписчики есть.

В версии 5 Redis предоставляет новую функцию под названием Redis Stream.

Идея Redis Stream связана с Redis Pub / Sub, но есть некоторые фундаментальные различия в способах использования данных. Основное различие между ними - способ хранения сообщений. В Pub / Sub сообщения отправляются и никогда не сохраняются. В Stream все сообщения хранятся в потоке на неопределенный срок, и это позволяет различным потребителям узнать, что за новое сообщение с его точки, запомнив идентификатор последнего сообщения.

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

В этой статье мы будем использовать Redis Stream для нескольких подключений для обмена данными.

Обзор реализации

  • Настроить сервер Redis
  • Настроить эхо-сервер
  • Настроить GraphQL
  • Включите WebSocket CORS в GraphQL
  • Настроить клиент Redis
  • Реализовать GraphQL Resolver
  • Настроить приложение React

Настроить сервер Redis

Сначала мы создадим сервер Redis с помощью Docker.

Запишите конфигурацию в docker-compose.yml:

version: '3'
services:
  redis:
    image: redis:5.0.7
    volumes:
      - redis-data:/data
    ports:
      - '6379:6379'
volumes:
  redis-data:
    driver: local

И запустите сервер, выполнив команду:

docker-compose up

Если вы столкнетесь с таким сообщением:

...
WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
...

Вам нужно будет настроить его на виртуальной машине для Docker для Mac.

Войдите в виртуальную машину в Docker для Mac, выполнив эту команду:

docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh

И поместите это на терминал:

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

Выйдите из терминала и запустите его снова:

docker-compose up

Настроить эхо-сервер

Далее мы настроим сервер с помощью пакета Echo.

Создайте main.go и напишите такой код:

И вы можете увидеть страницу Welcome по адресу http: // localhost: 8080:

Настроить GraphQL

Далее мы настроим GraphQL с помощью gqlgen.

Чтобы инициализировать пакет, выполните эту команду:

go run github.com/99designs/gqlgen init

Это создаст в вашем проекте следующие макеты:

├── go.mod
├── go.sum
├── gqlgen.yml
├── graph
│   ├── generated
│   │   └── generated.go
│   ├── model
│   │   └── models_gen.go
│   ├── resolver.go
│   ├── schema.graphqls
│   └── schema.resolvers.go
└── server.go

server.go должен быть точкой входа в этом макете, но мы удалим его и вместо этого поместим обработчик gqlgen в main.go:

Перейдите по адресу http: // localhost: 8080 / игровая площадка, и вы увидите страницу детской площадки:

Чтобы упростить код внутри main.go, мы извлечем код, связанный с маршрутизацией на infrastructure/router/router.go, и импортируем его следующим образом:

Включите WebSocket CORS в GraphQL

gqlgen имеет транспорт для WebSocket из коробки, поэтому нам не нужно его настраивать.

Однако он не включает реализацию CORS, поэтому нам нужно добавить ее вручную.

Для этого добавим infrastructure/graphql/server.go вот так:

В этом посте мы будем принимать любые входящие запросы от клиентов.

И импортируйте его в main.go вот так:

Теперь мы готовы установить соединение WebSocket на нашем сервере.

Настроить Redis Client

Далее мы настроим клиент Redis в нашем приложении.

Сначала создайте файл конфигурации в infrastructure/datastore/redis.go следующим образом:

И импортируйте это в main.go:

Реализовать Redis Stream

Далее мы реализуем Redis Stream в нашем приложении.

Давайте добавим код в graph/resolver.go вот так:

SubscribeRedis будет прослушивать Redis Stream и предоставлять данные по каналу.

В этом посте мы создадим поток под названием room для хранения наших данных. Чтобы периодически получать данные в потоке, нам нужно прослушивать поток через XREAD с опцией BLOCK. С опцией BLOCK поток будет ждать определенное количество времени, пока не будет добавлено новое входящее сообщение. В этом случае мы указываем 0, что означает, что он будет ждать, пока не придет сообщение.

Для получения данных нам нужно указать ID. В этом случае мы укажем $ для ID, что означает, что он будет получать сообщения только после того, как XREAD будет выпущен.

streams, err := r.RedisClient.XRead(&redis.XReadArgs{
   Streams: []string{"room", "$"},
   Block:   0,
}).Result()

Перейдите на main.go и запустите подписку:

И создайте маршрут для Подписки в infrastructure/router/router.go вот так:

Реализовать GraphQL Resolver

На последнем этапе на нашем сервере мы добавим реализацию в нашу схему.

Сначала перейдите к graph/schema.graphqls и добавьте схему следующим образом:

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

go run github.com/99designs/gqlgen

Это сгенерировало graph/schema.resolvers.go с функциями, основанными на схеме, которую мы определили выше.

Добавим реализацию в graph/schema.resolvers.go вот так:

Теперь мы подготовили наш сервер, чтобы периодически отправлять сообщения клиентам.

Настроить клиентское приложение

Чтобы быстро начать, мы будем использовать Create React App для создания приложения React.

Выполните эту команду в корне:

npx create-react-app frontend --template typescript

После установки запустите сервер разработки, выполнив эту команду:

yarn start

Вы увидите страницу приветствия:

Настройка клиента Apollo

Мы будем использовать Apollo Client в качестве клиентского модуля GraphQL.

Установим нужные нам пакеты:

cd frontend
yarn add @apollo/client graphql subscriptions-transport-ws

И создайте клиента Apollo на frontend/src/lib/apolloClient.ts:

И заверните компонент App в ApolloProvider с клиентом:

Когда вы запустите интерфейсный сервер, вы увидите, что HTTP-соединение успешно обновило WebSockets.

Реализовать компонент

Далее мы создадим компонент для отображения сообщений.

Сначала мы установим Chakra UI, чтобы создать нашу форму:

yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion

Создайте src/Component.ts и введите такой код:

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

И включите его в src/App.ts:

Теперь мы готовы к тестированию нашего приложения.

Давайте подготовим две вкладки в браузере и отправим сообщение.

Как видите, сообщение отображается на двух вкладках одновременно. А после перезагрузки отображаются все сообщения, хранящиеся в Redis Stream.

Теперь мы достигли связи в реальном времени с GraphQL и Redis Stream.

Заключение

Теперь мы рассмотрели, как реализовать подписку GraphQL с помощью gqlgen и Redis Stream. На стороне интерфейса мы реализовали чат в реальном времени с помощью Apollo Client. Если вы будете следовать этому руководству и применять его шаг за шагом, вы обнаружите, что это не так уж и сложно.

Вот и финальная кодовая база.

Надеюсь, вы сочтете это полезным.