Повышение навыков GraphQL: реальные подписки

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

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

В этой статье я хотел углубить свои познания в GraphQL, чтобы найти то, что можно было бы найти.

В моей статье “When It’s Time to Give REST a Rest” я говорил о том, что есть реальные сценарии, когда GraphQL более предпочтительна, чем RESTful-сервис. Мы прошли процесс создания и развертывания GraphQL-API с использованием Apollo Server.

В этой последовательной публикации я планирую улучшить свои познания о GraphQL, пройдясь по подпискам для реального времени при получении данных.我们 также создадим WebSocket-сервис для потребления подписок.

Резюме: Use Case Customer 360

Моя предыдущая статья была посвящена use case Customer 360, в котором клиенты моей вымышленной компании поддерживают следующие коллекции данных:

  • Информация о клиентах
  • Информация о адресах
  • Способы связи
  • Кредитные характеристики

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

JavaScript

 

type Query {
    addresses: [Address]
    address(customer_token: String): Address
    contacts: [Contact]
    contact(customer_token: String): Contact
    customers: [Customer]
    customer(token: String): Customer
    credits: [Credit]
    credit(customer_token: String): Credit
}

Использование RESTful- подхода для получения единого (360-gradowego) представления клиента было бы необходимо для выполнения нескольких запросов и ответов, которые было бы необходимо соединить. GraphQL предоставляет для нас решение, которое работает намного лучше.

Цели повышения уровня

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

  • Понимание и реализация ценности подписок в GraphQL
  • Использование WebSocket реализации для потребления GraphQL подписки

Идея использования подписок вместо запросов и мутаций в GraphQL является preferable методом, когда выполняются следующие условия:

  • Малые, постоянные изменения к большим объектам
  • Низкая задержка, реальное время обновления (например, чат-приложение)

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

К счастью, сценарий, который мы будем реализовывать в нашем примере Customer 360, идеально подходит для подписок. Также мы будем использовать WebSocket- подход для использования этих подписок.

Как и ранее, я продолжу использовать Apollo.

Достижение уровня повышения счетов.

Сначала нам нужно установить необходимые библиотеки для поддержки подписок на моем сервере Apollo GraphQL:

Shell

 

npm install ws
npm install graphql-ws @graphql-tools/schema
npm install graphql-subscriptions 

Установив эти элементы, я сосредоточился на обновлении файла index.ts из моего исходного репозитория, чтобы расширить константу typedefs следующим образом:

JavaScript

 

type Subscription {
    creditUpdated: Credit
}

Я также установил постоянную для хранения нового экземпляра PubSub и создал пример подписки, которую мы будем использовать позже:

JavaScript

 

const pubsub = new PubSub();

pubsub.publish('CREDIT_BALANCE_UPDATED', {
    creditUpdated: {
    }
});

Я очистил существующие резолверы и добавил новый Subscription для этого нового использования:

JavaScript

 

const resolvers = {
    Query: {
        addresses: () => addresses,
        address: (parent, args) => {
            const customer_token = args.customer_token;
            return addresses.find(address => address.customer_token === customer_token);
        },
        contacts: () => contacts,
        contact: (parent, args) => {
            const customer_token = args.customer_token;
            return contacts.find(contact => contact.customer_token === customer_token);
        },
        customers: () => customers,
        customer: (parent, args) => {
            const token = args.token;
            return customers.find(customer => customer.token === token);
        },
        credits: () => credits,
        credit: (parent, args) => {
            const customer_token = args.customer_token;
            return credits.find(credit => credit.customer_token === customer_token);
        }
    },
    Subscription: {
        creditUpdated: {
            subscribe: () => pubsub.asyncIterator(['CREDIT_BALANCE_UPDATED']),
        }
    }
};

После этого я рефакторировал конфигурацию сервера и внедрил дизайн подписок:

JavaScript

 

const app = express();
const httpServer = createServer(app);
const wsServer = new WebSocketServer({
    server: httpServer,
    path: '/graphql'
});

const schema = makeExecutableSchema({ typeDefs, resolvers });
const serverCleanup = useServer({ schema }, wsServer);

const server = new ApolloServer({
    schema,
    plugins: [
        ApolloServerPluginDrainHttpServer({ httpServer }),
        {
            async serverWillStart() {
                return {
                    async drainServer() {
                        serverCleanup.dispose();
                    }
                };
            }
        }
    ],
});

await server.start();

app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
    context: async () => ({ pubsub })
}));

const PORT = Number.parseInt(process.env.PORT) || 4000;
httpServer.listen(PORT, () => {
    console.log(`Server is now running on http://localhost:${PORT}/graphql`);
    console.log(`Subscription is now running on ws://localhost:${PORT}/graphql`);
});

Чтобы имитировать инициированные клиентом обновления, я создал следующий метод, увеличивающий баланс кредитов на $50 каждые пять секунд, пока служба работает. Когда баланс достигает (или превышает) лимит кредитов в $10,000, я возвращаю баланс обратно до $2,500, имитируя оплату баланса.

JavaScript

 

function incrementCreditBalance() {
    if (credits[0].balance >= credits[0].credit_limit) {
        credits[0].balance = 0.00;
        console.log(`Credit balance reset to ${credits[0].balance}`);

    } else {
        credits[0].balance += 50.00;
        console.log(`Credit balance updated to ${credits[0].balance}`);
    }

    pubsub.publish('CREDIT_BALANCE_UPDATED', { creditUpdated: credits[0] });
    setTimeout(incrementCreditBalance, 5000);
}

incrementCreditBalance();

Полный файл index.ts можно найти здесь.

Деплой на Heroku

Сервис готов, теперь нам надо выпустить его, чтобы мы могли взаимодействовать с ним. since Heroku worked out great last time (and it’s easy for me to use), let’s stick with that approach.

Для начала мне потребовалось выполнить следующие команды CLI Heroku:

Shell

 

$ heroku login
$ heroku create jvc-graphql-server-sub

Creating jvc-graphql-server-sub... done
https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-sub.git

Команда также автоматически добавила репозиторий, используемый Heroku, как удаленный:

Shell

 

$ git remote
heroku
origin

Как я указал в моем предыдущем статье, Apollo Server отключает Apollo Explorer в производственных средах. Чтобы сохранить Apollo Explorer для наших нужд, мне нужно было установить переменную среды NODE_ENV на developmen. Я это сделал следующим CLI-командой:

Shell

 

$ heroku config:set NODE_ENV=development

Setting NODE_ENV and restarting jvc-graphql-server-sub... done, v3
NODE_ENV: development

Я был готов выпустить мой код на Heroku:

Shell

 

$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku

быстро проверив Heroku Dashboard, я увидел, что мой Apollo Server работает без каких-либо проблем:

В разделе Settings, я нашел URL приложения Heroku для этого экземпляра сервиса:

https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/

  • Обратите внимание: Этот ссылка больше не будет работать к тому времени, когда статья будет опубликована.

Пока что я мог добавить graphql к этому URL, чтобы запустить Apollo Server Studio. Это позволило мне увидеть, что подписки работают как ожидается:

Обратите внимание на ответы подписок с правой стороны экрана.

Улучшение навыков с помощью WebSocket

Мы можем использовать поддержку WebSocket и возможности Heroku, чтобы создать реализацию, которая потребляет созданную нами подписку.

В моей ситуации я создал файл index.js с следующим содержимым. Этот код создает клиент WebSocket и также устанавливает имитационный HTTP-сервис, который я могу использовать для проверки работы клиента:

JavaScript

 

import { createClient } from "graphql-ws";
import { WebSocket } from "ws";
import http from "http";

// Создать имитационный HTTP-сервис, чтобы привязаться к $PORT Heroku'a
const PORT = process.env.PORT || 3000;
http.createServer((req, res) => res.end('Server is running')).listen(PORT, () => {
  console.log(`HTTP server running on port ${PORT}`);
});

const host_url = process.env.GRAPHQL_SUBSCRIPTION_HOST || 'ws://localhost:4000/graphql';

const client = createClient({
  url: host_url,
  webSocketImpl: WebSocket
});

const query = `subscription {
  creditUpdated {
    token
    customer_token
    credit_limit
    balance
    credit_score
  }
}`;

function handleCreditUpdated(data) {
  console.log('Received credit update:', data);
}

// Подписаться на подписку creditUpdated
client.subscribe(
  {
    query,
  },
  {
    next: (data) => handleCreditUpdated(data.data.creditUpdated),
    error: (err) => console.error('Subscription error:', err),
    complete: () => console.log('Subscription complete'),
  }
);

Полный файл index.js можно найти здесь.

Мы можем также развернуть эту простую приложение Node.js на Heroku, убедившись, что для неё установлен environment variable GRAPHQL_SUBSCRIPTION_HOST на URL приложения Heroku, который мы использовали ранее.

Я также создал следующий Procfile, чтобы сказать Heroku, как запустить мое приложение:

Shell

 

web: node src/index.js

Далее, я создал новый приложение Heroku:

Shell

 

$ heroku create jvc-websocket-example

Creating jvc-websocket-example... done
https://jvc-websocket-example-62824c0b1df4.herokuapp.com/ | https://git.heroku.com/jvc-websocket-example.git

После этого, я установил environment variable GRAPHQL_SUBSCRIPTION_HOST, направляя его на мой запущенный сервер GraphQL:

Shell

 

$ heroku --app jvc-websocket-example \
    config:set \
GRAPHQL_SUBSCRIPTION_HOST=ws://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/graphql

В этой точке мы готовы развернуть на Heroku наш код:

Shell

 

$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku

Когда запускается клиент WebSocket, статус его можно увидеть на панели управления Heroku:

Просмотрев логи на панели управления Heroku для экземпляра jvc-websocket-example, можно увидеть многочисленные обновления свойства balance для службы jvc-graphql-server-sub. В моем демонстрационном примере я даже смог захватить用例, когда значение balance снижается до нуля, имитируя, что произошла транзакция оплаты:

В терминале мы можем получить те же логи с помощью команды CLI heroku logs.

Shell

 

2024-08-28T12:14:48.463846+00:00 app[web.1]: Received credit update: {
2024-08-28T12:14:48.463874+00:00 app[web.1]:   token: 'credit-token-1',
2024-08-28T12:14:48.463875+00:00 app[web.1]:   customer_token: 'customer-token-1',
2024-08-28T12:14:48.463875+00:00 app[web.1]:   credit_limit: 10000,
2024-08-28T12:14:48.463875+00:00 app[web.1]:   balance: 9950,
2024-08-28T12:14:48.463876+00:00 app[web.1]:   credit_score: 750
2024-08-28T12:14:48.463876+00:00 app[web.1]: }

Не только мы имеем GraphQL-сервис с реализацией подписок, но теперь у нас также есть клиент WebSocket, потребляющий эти обновления.

Заключение

Мои читатели, скорее всего, помнят мое личное заявление о миссии, которое, по-моему, может примениться к любому IT-профессионалу:

“Сосредотачивайте время на доставке функциональности, расширяющей ценность вашего интеллектуального достояния. Использование фреймворков, продуктов и услуг для всего остального.”

— J. Vester

В глубоком исследовании подписок GraphQL мы успешно использовали обновления от Apollo Server, работающего на Heroku, с помощью другой услуги, также работающей на Heroku — Node.js-базированного приложения, использующего WebSockets. Благодаря лёгким подпискам мы избежали отправки запросов для неизменяемой информации, а просто подписались на получение обновлений баланса кредитов в момент их возникновения.

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

Heroku является еще одним примером, который следует за моим манифестом, предлагая платформу, которая позволяет быстро создавать прототипы решений с использованием CLI и стандартных Git-команд. Это не только дает мне простой способ продемонстрировать использование моего casuse-subscribe, но и внедрить потребителя с использованием WebSockets.

Если вы заинтересованы в исходном коде для этой статьи, посмотрите на мои репозитории на GitLab:

Я уверен, что мои навыки работы с GraphQL уровень улучшился благодаря этому усилию. Этот путешествие было для меня новым и сложным — а также очень веселым!

Я планирую в следующем идти на авторизацию, которая, надеюсь, предоставит еще одну возможность улучшить свои навыки в GraphQL и Apollo Server. Оставайтесь с нами!

Имейте очень хороший день!

Source:
https://dzone.com/articles/leveling-up-my-graphql-skills-real-time-subscriptions