GraphQL появился относительно недавно, а значит, разработчики API не уверены, писать следующий API на GraphQL или нет. Очевидно, что REST API хорошо знаком программистам и уверенно используется в работе. Кроме того, в использовании GraphQL много открытых вопросов, среди которых версионирование, аутентификация и авторизация, кэширование, пагинация и другие.
Задача поста
Этот пост поможет понять, какие части в REST API требуют изменения. Здесь показано, какие компоненты возможно объединить для создания GraphQL. В нашем распоряжении набор REST API, написанных с express.js в Node.js, а к концу поста мы перенесем их на GraphQL. Ниже ссылка на репозиторий.
https://github.com/haldarmahesh/migrate-rest-api-to-graphql
Также создан пул реквест, чтобы показать, какие конкретно файлы меняются при миграции. Если интересно, посмотрите на него в приведенном выше репозитории.
REST API для миграции
Сначала быстро взглянем на уже написанные API и структуру написания сервиса. Мы находимся на одной странице и решаем, какую часть кодовой базы изменить.
Это приложение Node.js предоставляет список API, в которых возможны следующие действия:
GET: /api/employees
возвращает список всех сотрудников.GET: /api/employess/:id
возвращает информацию о сотруднике с идентификатором, переданным в качестве параметра.POST: /api/employees
создает нового сотрудника.DELETE: /api/employees/:id
удаляет сотрудника, идентификатор которого передается как параметр.
Этот список программных интерфейсов будет охватывать все типы API, разработанные в большинстве приложений. Технический слой будет таким же. Отличаться будет только уровень бизнес-логики или вариантов использования.
Ниже приведен фрагмент кода из существующего node-приложения, где создаются эти API.
const express = require('express');
const router = express.Router();
const employeeService = require('../service/employeeService');
router.get('/api/employees', (req, res) => res.json(employeeService.getAll()));
router.get('/api/employees/:id', (req, res) => {
const employeeId = req.params.id;
return res.json(employeeService.getById(employeeId));
});
router.post('/api/employees', (req, res) => {
const newEmployee = req.body;
return res.json(employeeService.save(newEmployee));
});
router.delete('/api/employees/:id', (req, res) => {
const employeeId = req.params.id;
return res.json(employeeService.deleteById(employeeId)); // todo convert to do
});
module.exports = router;
Если хотите запустить сервер и проверить реализацию, обратитесь к этому репозиторию.
Фрагмент кода выше — контроллер — отображение между API и различными сервисами. Например, GET /api/employees/:id
вызовет функцию employeeService.getById(employeeId)
из employeeService
. API этого приложения очень соответствуют распространенному дизайну слоёв сервиса, контроллера и репозитория.
- Слой контроллера — мост между клиентской стороной и сервисами.
- Сервисный слой отвечает за бизнес-логику.
- Слой репозитория отвечает за взаимодействие с источником данных или базой данных и их передачу на слой сервиса.
Давайте посмотрим, как это поможет поддерживать приложение. Предположим, мы хотим изменить базу данных с MySQL на Postgres, значит, меняем только код на уровне репозитория, а контроллер и сервиcный слой останутся нетронутыми. Эта абстракция помогает в определении границ и эффективно распределяет обязанности. Такой дизайн также поможет перейти с REST на GraphQL. Нужно только внести изменения в слой контроллера, всё остальное остаётся прежним.
Слой контроллера GraphQL
Слой контроллера GraphQL — самый тонкий слой — интерфейс между клиентом и сервисом. В REST API есть только один URL для конкретного API, который заканчивается сотнями конечных точек, а в GraphQL предоставляется только одна конечная точка. Это означает, что контроллер GraphQL имеет только один URL.
POST /graphql
— и весь запрос передаётся этой конечной точке в качестве тела.
Сосуществование GraphQL и REST
Поскольку GraphQL — просто ещё одна конечная точка POST, API REST и GraphQL могут одновременно находиться в одном программном интерфейсе. Это полезно, если вы хотите осторожно перейти на GraphQL, не удаляя REST.
Этапы миграции
Шаг 1. Устанавливаем GraphQL и зависимости:
npm install express express-graphql graphql --save
Шаг 2. Создаём новую конечную точку в Express:
const graphqlController = require('./controller/graphqlController');
app.use('/graphql', graphqlController);
Шаг 3. Создаём файл контроллера и собираем схемы GraphQL:
// src/controller/graphqlController.js
const graphqlHTTP = require('express-graphql');
const { buildSchema } = require('graphql');
// Создание схемы с использованием языка GraphQL
const schema = buildSchema(`
type Query {
employee(id: Int!): Employee # `!` signifies this is mandatory
employees: [Employee],
}
type Mutation {
createEmployee(name: String!, department: String!): Employee,
deleteEmployee(id: Int!): DeleteEmployeeResponse
}
input EmployeeInput {
name: String!,
department: String!
}
type DeleteEmployeeResponse {
id: Int!
}
type Employee {
id: Int!,
name: String!,
department: String!
}
`);
// свяжите эту схему с GraphQL и сервисом для вызова Query и Mutation
В приведенном выше фрагменте кода строка 8: тип Query
содержит типы объектов, доступных другим API, а тип Mutation
содержит API создания, обновления и удаления.
Строка 9: схема GraphQL строго типизирована, а значит, нужно упомянуть типы: id
имеет тип Int
, если введёте строку, то это вызовет ошибку! Оператор !
показывает, что значение обязательно.
В основном тип Query
нужен для получения запросов, а Mutation
— для чего-то, что изменяет значения.
Схема в строке 7 — важнейший блок GraphQL. Она помогает в настройке документации для клиента, желающего сделать запрос. Давайте посмотрим, как эта схема создаёт документацию.
Выше — скриншот GraphQL, показывающий интерфейс запросов, что-то вроде документации. В самом левом разделе пишете запрос, эквивалентный телу запроса POST, если пытаетесь использовать Postman или другой инструмент, а небольшое меню — выпадающий список полей, которые можно запрашивать в запросе сотрудников.
Раздел справа — интерактивный проводник запросов и API мутации, перечисляющий входные данные и тип возвращаемого значения.
Средняя часть — ответ на запрос.
Шаг 4. Связываем контроллер и сервисы:
Теперь связываем Query
и Mutation
с существующими сервисами. В следующем фрагменте кода строки 9 и 31 на самом деле связывают схему с сервисом, ключ employee
находит и вызывает функцию, определенную в rootResolver
(строка 31). Т.е. employeeService.getById
в реализации REST была определена и привязана к router.get
, смотрите ниже.
// Реализация REST для getEmployeeById
router.get('/api/employees/:id', (req, res) => {
const employeeId = req.params.id;
return res.json(employeeService.getById(employeeId));
});
Аналогично, для строки 13 и 33 используется тот же ключ и вызывается функция employeeService.save
.
// src/controller/graphqlController.js
const graphqlHTTP = require('express-graphql');
const { buildSchema } = require('graphql');
// Создание схемы с использованием языка GraphQL
const schema = buildSchema(`
type Query {
employee(id: Int!): Employee
employees: [Employee],
}
type Mutation {
createEmployee(name: String!, department: String!): Employee,
deleteEmployee(id: Int!): DeleteEmployeeResponse
}
input EmployeeInput {
name: String!,
department: String!
}
type DeleteEmployeeResponse {
id: Int!
}
type Employee {
id: Int!,
name: String!,
department: String!
}
`);
const rootResolver = {
employee: graphqlInput => employeesService.getById(graphqlInput && graphqlInput.id),
employees: employeesService.getAll(),
createEmployee: graphqlInput => employeesService.save(graphqlInput),
deleteEmployee: graphqlInput => employeesService.deleteById(graphqlInput.id),
};
const graphql = graphqlHTTP({
schema,
rootValue: rootResolver,
graphiql: true, // создается интерактивный проводник GraphQL API с документацией.
});
module.exports = graphql;
В строке 40, если для graphiql
установлено true
, то создаётся удивительное интерактивное окно для проверки документации и работы с GraphQL.
Примечание: если не придерживаетесь схемы разделения слоя контроллера и сервисного слоя, а реализация тесно связана с конечной точкой API в контроллере, то можете извлечь код из функции, которая может выступать в качестве псевдосервиса, или напишите свой сервисный слой.
Шаг 5. Настройте graphqlHttp
, схемы и резолверы.
Последний шаг — использование graphqlHTTP
, установка схемы и определение того, какой ключ в схеме вызывает какую функцию или сервис. В приведенном выше коде смотрите со строки 37.
Демо
Если хотите быстро поэкспериментировать с GraphQL, воспользуйтесь этой песочницей. Нажмите Ctrl+Space и увидите выпадающее меню. Прочтите руководство в песочнице, чтобы больше узнать о том, как написать запрос GraphQL.
Вы можете клонировать этот репозиторий, перейти в ветку feature/migrate-to-graphql
и запустить сервер, перейдя на http://localhost:3000
в вашем браузере.
Разница между REST API и GraphQL API в деталях
Чтобы показать, что именно меняется при переходе с REST на GraphQL, в том же репозитории я создал пул реквест. Там вы можете увидеть, что нужно изменить в вашей кодовой базе.
https://github.com/haldarmahesh/migrate-rest-api-to-graphql/pull/2/files
Вот и всё! Я надеюсь, что этот пост помог вам.
Читайте также:
- Лучшие JavaScript библиотеки за 2019 год для построения диаграмм
- Rest и Spread в JavaScript. Возможности, о которых вы не знали
Перевод статьи Mahesh Haldar: Migrating Existing REST APIs to GraphQL