За годы создания сервисов RESTful API был моим основным выбором. Однако даже несмотря на то, что у REST есть свои преимущества, это не означает, что это лучший подход для каждого варианта использования. За прошедшие годы я понял, что иногда для определенных сценариев могут существовать лучшие альтернативы. Придерживаться REST только потому, что он мне нравится (когда он мне не подходит), приводит только к технологическому долгу и натянутым отношениям с владельцем продукта.
Одна из самых больших болевых точек подхода RESTful — необходимость делать несколько запросов для получения всей необходимой информации для принятия бизнес-решения.
В качестве примера предположим, что мне нужен обзор клиента на 360 градусов. Мне нужно будет сделать следующие запросы:
GET /customers/{some_token}
предоставляет базовую информацию о клиенте.GET /addresses/{some_token}
предоставляет необходимый адрес.GET /contacts/{some_token}
возвращает контактную информацию.GET /credit/{some_token}
возвращает ключевую финансовую информацию.
Хотя я понимаю, что основная цель REST — обеспечить четкое реагирование на каждый ресурс, этот сценарий требует больше работы со стороны потребителя. Чтобы заполнить пользовательский интерфейс, который поможет организации принимать решения, связанные с будущим бизнесом с клиентом, потребитель должен сделать несколько звонков.
В этой статье я покажу, почему GraphQL является предпочтительным подходом по сравнению с RESTful API, демонстрируя, как развернуть сервер Apollo (и Apollo Explorer), чтобы быстро приступить к работе с GraphQL.
Я планирую создать свое решение с помощью Node.js и развернуть его в Heroku.
Существует несколько распространенных случаев использования, когда GraphQL является лучшим подходом, чем REST:
Поскольку мои навыки работы с GraphQL еще не отточены, для этой статьи я решил использовать Apollo Server .
Apollo Server — это сервер GraphQL, который работает с любой схемой GraphQL. Цель — упростить процесс создания API GraphQL. Базовый дизайн хорошо интегрируется с такими платформами, как Express или Koa. В моей следующей статье я рассмотрю возможность использования подписок (через библиотекуgraphql-ws ) для получения данных в реальном времени.
В чем Apollo Server действительно хорош, так это в Apollo Explorer, встроенном веб-интерфейсе, который разработчики могут использовать для изучения и тестирования своих API-интерфейсов GraphQL. Студия мне подойдет идеально, так как позволяет легко создавать запросы и просматривать схему API в графическом формате.
В этом примере предположим, что нам нужна следующая схема, чтобы обеспечить обзор клиента на 360 градусов:
type Customer { token: String name: String sic_code: String } type Address { token: String customer_token: String address_line1: String address_line2: String city: String state: String postal_code: String } type Contact { token: String customer_token: String first_name: String last_name: String email: String phone: String } type Credit { token: String customer_token: String credit_limit: Float balance: Float credit_score: Int }
Я планирую сосредоточиться на следующих запросах GraphQL:
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 }
Потребители предоставят токен Клиенту, которого они хотят просмотреть. Мы ожидаем также получить соответствующие объекты Address, Contact и Credit. Цель состоит в том, чтобы избежать четырех разных вызовов API для всей этой информации, а не делать один вызов API.
Я начал с создания новой папки под graphql-server-customer
на моей локальной рабочей станции. Затем, используя раздел «Начало работы» документации Apollo Server, я выполнил первый и второй шаги, используя машинописный подход.
Затем я определил свою схему, а также включил некоторые статические данные для тестирования. Обычно мы подключаемся к базе данных, но для этой демонстрации вполне подойдут статические данные.
Ниже мой обновленный файл index.ts
:
import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; const typeDefs = `#graphql type Customer { token: String name: String sic_code: String } type Address { token: String customer_token: String address_line1: String address_line2: String city: String state: String postal_code: String } type Contact { token: String customer_token: String first_name: String last_name: String email: String phone: String } type Credit { token: String customer_token: String credit_limit: Float balance: Float credit_score: Int } 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 } `; const resolvers = { Query: { addresses: () => addresses, address: (parent, args, context) => { const customer_token = args.customer_token; return addresses.find(address => address.customer_token === customer_token); }, contacts: () => contacts, contact: (parent, args, context) => { const customer_token = args.customer_token; return contacts.find(contact => contact.customer_token === customer_token); }, customers: () => customers, customer: (parent, args, context) => { const token = args.token; return customers.find(customer => customer.token === token); }, credits: () => credits, credit: (parent, args, context) => { const customer_token = args.customer_token; return credits.find(credit => credit.customer_token === customer_token); } }, }; const server = new ApolloServer({ typeDefs, resolvers, }); const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }); console.log(`Apollo Server ready at: ${url}`); const customers = [ { token: 'customer-token-1', name: 'Acme Inc.', sic_code: '1234' }, { token: 'customer-token-2', name: 'Widget Co.', sic_code: '5678' } ]; const addresses = [ { token: 'address-token-1', customer_token: 'customer-token-1', address_line1: '123 Main St.', address_line2: '', city: 'Anytown', state: 'CA', postal_code: '12345' }, { token: 'address-token-22', customer_token: 'customer-token-2', address_line1: '456 Elm St.', address_line2: '', city: 'Othertown', state: 'NY', postal_code: '67890' } ]; const contacts = [ { token: 'contact-token-1', customer_token: 'customer-token-1', first_name: 'John', last_name: 'Doe', email: '[email protected]', phone: '123-456-7890' } ]; const credits = [ { token: 'credit-token-1', customer_token: 'customer-token-1', credit_limit: 10000.00, balance: 2500.00, credit_score: 750 } ];
Когда все настроено как положено, мы запускаем следующую команду для запуска сервера:
$ npm start
Поскольку сервер Apollo работал на порту 4000, я использовал URL-адрес http://localhost:4000/ для доступа к Apollo Explorer. Затем я создал следующий пример запроса:
query ExampleQuery { addresses { token } contacts { token } customers { token } }
Вот как это выглядит в Apollo Explorer:
Нажав кнопку «Пример запроса» , я убедился, что полезная нагрузка ответа соответствует статическим данным, которые я предоставил в index.ts
:
{ "data": { "addresses": [ { "token": "address-token-1" }, { "token": "address-token-22" } ], "contacts": [ { "token": "contact-token-1" } ], "customers": [ { "token": "customer-token-1" }, { "token": "customer-token-2" } ] } }
Прежде чем перейти к дальнейшему рассмотрению моего варианта использования Customer 360, я хотел запустить этот сервис в облаке.
Поскольку эта статья посвящена чему-то новому, я хотел посмотреть, насколько сложно будет развернуть мой сервер Apollo на Heroku.
Я знал, что мне нужно устранить различия в номерах портов при локальной работе и работе где-то в облаке. Я обновил свой код для запуска сервера, как показано ниже:
const { url } = await startStandaloneServer(server, { listen: { port: Number.parseInt(process.env.PORT) || 4000 }, });
В этом обновлении мы будем использовать порт 4000, если в переменной среды не указано значение PORT.
Используя Gitlab, я создал новый проект для этих файлов и вошел в свою учетную запись Heroku, используя интерфейс командной строки (CLI):
$ heroku login
Вы можете создать новое приложение в Heroku с помощью интерфейса командной строки или веб-интерфейса панели управления Heroku. В этой статье мы будем использовать CLI:
$ heroku create jvc-graphql-server-customer
Команда CLI вернула следующий ответ:
Creating ⬢ jvc-graphql-server-customer... done https://jvc-graphql-server-customer-b62b17a2c949.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-customer.git
Команда также автоматически добавила репозиторий, используемый Heroku в качестве удаленного:
$ git remote heroku origin
По умолчанию Apollo Server отключает Apollo Explorer в производственных средах. Для моей демо-версии я хочу оставить ее работающей на Heroku. Для этого мне нужно установить переменную среды NODE_ENV
для разработки. Я могу установить это с помощью следующей команды CLI:
$ heroku config:set NODE_ENV=development
Команда CLI вернула следующий ответ:
Setting NODE_ENV and restarting ⬢ jvc-graphql-server-customer... done, v3 NODE_ENV: development
Теперь мы можем развернуть наш код в Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku' $ git push heroku
Быстрый просмотр панели управления Heroku показывает, что мой сервер Apollo работает без каких-либо проблем:
Если вы новичок в Heroku, это руководство покажет вам, как создать новую учетную запись и установить Heroku CLI.
С помощью GraphQL я могу удовлетворить критериям приемлемости для моего варианта использования Customer 360 с помощью следующего запроса:
query CustomerData($token: String) { customer(token: $token) { name sic_code token }, address(customer_token: $token) { token customer_token address_line1 address_line2 city state postal_code }, contact(customer_token: $token) { token, customer_token, first_name, last_name, email, phone }, credit(customer_token: $token) { token, customer_token, credit_limit, balance, credit_score } }
Все, что мне нужно сделать, это передать одну переменную token
клиента со значением customer-token-1
:
{ "token": "customer-token-1" }
Мы можем получить все данные, используя один вызов API GraphQL:
{ "data": { "customer": { "name": "Acme Inc.", "sic_code": "1234", "token": "customer-token-1" }, "address": { "token": "address-token-1", "customer_token": "customer-token-1", "address_line1": "123 Main St.", "address_line2": "", "city": "Anytown", "state": "CA", "postal_code": "12345" }, "contact": { "token": "contact-token-1", "customer_token": "customer-token-1", "first_name": "John", "last_name": "Doe", "email": "[email protected]", "phone": "123-456-7890" }, "credit": { "token": "credit-token-1", "customer_token": "customer-token-1", "credit_limit": 10000, "balance": 2500, "credit_score": 750 } } }
Ниже приведен снимок экрана Apollo Explorer, запущенного из моего приложения Heroku:
Я вспоминаю, как в начале своей карьеры Java и C# конкурировали друг с другом за принятие разработчиками. Сторонники каждой стороны дебатов были готовы доказать, что выбранная ими технология была лучшим выбором… даже если это было не так.
В этом примере мы могли бы реализовать мой вариант использования Customer 360 несколькими способами. Использование проверенного RESTful API могло бы сработать, но для получения всех необходимых данных потребовалось бы несколько вызовов API. Использование Apollo Server и GraphQL позволило мне достичь своих целей с помощью одного вызова API.
Мне также нравится, как легко развернуть мой сервер GraphQL в Heroku с помощью всего лишь нескольких команд в моем терминале. Это позволяет мне сосредоточиться на реализации — разгрузить инфраструктуру и передать свой код доверенному стороннему поставщику. Самое главное, это полностью соответствует моей личной миссии:
«Сосредоточьте свое время на предоставлении функций/функциональность, которые увеличивают ценность вашей интеллектуальной собственности. Используйте платформы, продукты и услуги для всего остального».
– Дж. Вестер
Если вас интересует исходный код этой статьи, он доступен на GitLab .
Но подождите… это еще не все!
В моем следующем посте мы продолжим дорабатывать наш сервер GraphQL, чтобы реализовать аутентификацию и получение данных в реальном времени с помощью подписок.
Хорошего дня!