Как обратить потоковый интерфейс ввода-вывода в GraphQL

kak obratit potokovyj interfejs vvoda vyvoda v graphql
изображение-252
Фото Эзры Комо-Джефри / Unsplash

В этом сообщении речь идет об использовании GraphQL для обработки службы, использующей поток ввода/вывода для взаимодействия между клиентом и сервером. В предыдущей публикации я имитировал API GraphQL для универсального шахматного интерфейса (UCI). UCI использует stdio для связи, принимая команды из входящего потока и посылая ответы через выходной поток. Я буду использовать UCI в качестве иллюстрации, но я не буду описывать UCI очень подробно.

Stockfish

Stockfish – это такой же известный шахматный двигатель, который поддерживает UCI. Используя NodeJS и модуль stockfish.js (транспиляция JavaScript оригинала), легко настроить работающую систему, реализующую UCI через stdio:

  • создать и записать в папку
  • npm init
  • npm install stockfish
  • node node_modules/stockfish/src/stockfish.js

И оттуда можно вводить команды UCI в окне терминала и видеть результаты.

Обзор запроса против мутации

Запросы выполняются параллельно. Это не проблема для API без состояния, где каждый запрос будет возвращать тот же результат независимо от порядка возврата результатов. UCI не являются лицами без гражданстваПоэтому команды и результаты должны действовать последовательно. Вот пример взаимодействия между клиентом командной строки и шахматным механизмом:

GUI     engine

// tell the engine to switch to UCI mode
uci

// engine identify  
    id name Shredder
		id author Stefan MK

// engine sends the options it can change
		option name Hash type spin default 1 min 1 max 128
		option name NalimovPath type string default 
		option name NalimovCache type spin default 1 min 1 max 32
// the engine has sent all parameters and is ready
		uciok

// now the GUI sets some values in the engine
// set hash to 32 MB
setoption name Hash value 32
setoption name NalimovCache value 1
setoption name NalimovPath value d:\tb;c\tb

// this command and the answer is required here!
isready

// engine has finished setting up the internal values
		readyok

// now we are ready to go
Эти команды взяты из документации UCI

Ответы двигателя на команды клиента имеют отступление. Первый переход состояния состоит в том, чтобы инициировать протокол UCI, где механизм отвечает параметрам по умолчанию и uciok сигнал, указывающий на завершение. На этом этапе клиент может настроить настройки. Они вступят в силу только тогда, когда команда готов кажется. Двигатель отвечает readyok когда установлены все параметры. Более поздние изменения состояния будут происходить при настройке игры и анализа (не показано).

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

import {makeExecutableSchema} from 'graphql-tools';

const typeDefs = `
type Query {
  message(id: ID!): String!
}
type Mutation {
  message(id: ID!): String!
}
`

const resolvers = {
  Query: {
    message: (_, {id}) => new Promise(resolve => {
      setTimeout(function() {
        let message = `response to message ${id}`;
        console.log(message)
        resolve(message);
      }, Math.random() * 10000)
    })
  },
  Mutation: {
    message: (_, {id}) => new Promise(resolve => {
      setTimeout(function() {
        let message = `response to message ${id}`;
        console.log(message)
        resolve(message);
      }, Math.random() * 10000)
    })
  }
}

const schema = makeExecutableSchema({typeDefs, resolvers});
export {
  schema
};

Результаты:

1*rqOQsfsW6HNp2ovNmvTSWQ
Порядок разрешения отличается от ответа.

В окнах консоли (нижняя половина) вы можете увидеть, когда были возвращены ответы. Теперь выполните те же запросы через Mutation:

1*6blqj1VzTMOohRjRHBG2zQ
Порядок решения соответствует порядку ответа

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

Что это значит для обертки GraphQL UCI

В предыдущей публикации я привел аргументы, почему GraphQL можно использовать для обертывания UCI. Возможно, самый простой способ сделать это – воспользоваться услугой подписки GraphQL. Это будет передавать события клиенту через веб-сокет. Команды отправляются по запросам или мутациям, а ответы возвращаются как события, на которые вы подписались.

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

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

  1. подписаться на ожидаемую реакцию
  2. отправить команду через HTTP
  3. получить ответ HTTP (подтверждение получения запроса, а не фактический результат)
  4. дождитесь поступления настоящего ответа через веб-сокет.
  5. действовать за ответом

Упрощение взаимодействия клиент-сервер

Разделим типы ответов, которые посылает UCI:

  1. однострочный ответ
  2. нет ответа
  3. многострочный, многозначный ответ с символом окончания

(Отдельно: можно начать анализ без определенного ограничения времени («бесконечно иди»). Это подпадает под категорию 2, поскольку анализ достигнет наилучшей точки завершения хода через истощение или через СТОП команда.)

Категория 1 это простой вызов и ответ, и их можно обрабатывать как обычные старые HTTP-запросы GraphQL. Нет необходимости подписываться на ответ: решатель может просто вернуть его, когда он придет.

Категория 2 не получает ответа от механизма, но ответ нужен HTTP. Все, что нужно в этом случае, это подтвердить запрос.

Категория 3 имеет два подтипа: запросы с многострочными, но фиксированными ответами (например, вариант), и запросы с потоковой передачей, промежуточные ответы (иди). Первое снова можно обрабатывать через HTTP, поскольку ответ будет предсказуемым и своевременным. Последний имеет разное (возможно, длительное) время завершения и может отправлять серию промежуточных ответов, интересующих клиента, которые он хотел бы получить в режиме реального времени.. Поскольку мы не можем отправить несколько ответов на запрос HTTP, этот последний случай не может быть обработан только HTTP, поэтому интерфейс подписки, как описано выше, все еще подходит.

Несмотря на то, что UCI является поточным интерфейсом, оказалось, что в большинстве случаев ответ/запрос HTTP можно использовать для взаимодействия через GraphQL.

Выводы

  1. Схема GraphQL должна состоять из мутаций, поскольку UCI имеет статус и команды должны выполняться последовательно
  2. Для команд Категории 1 и 2 запрос HTTP является самым простым. В бэк-энде все еще продолжается потоковая передача, но резолверы GraphQL создадут экземпляр слушателя потока UCI, соответствующего ожидаемому ответу на команду UCI, прежде чем отправлять команду механизму. Этот слушатель решит запрос GraphQL через HTTP, когда ответ поступит от механизма. Это упрощает работу клиента.
  3. Сервер также отслеживает состояние UCI, чтобы убедиться, что команды выполняются в надлежащем контексте. Если клиент пытается выполнить команду до того, как механизм сможет ее обработать, будет возвращена ошибка статуса HTTP.
  4. В тех случаях, когда отсутствует ожидаемый ответ от UCI, резолвер GraphQL просто подтвердит получение команды.
  5. Определенный случай для Категории 3 (где надежный и быстрый ответ) может обрабатываться с помощью HTTP.
  6. Неопределенный случай, когда есть промежуточные ответы перед прекращением, можно обрабатывать через веб-сокет. Это, в свою очередь, может быть включено в службу подписки GraphpQL.

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

Код этой статьи можно найти здесь.

Добавить комментарий

Ваш адрес email не будет опубликован.