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

Вот и всё! Я надеюсь, что этот пост помог вам.

Читайте также:


Перевод статьи Mahesh HaldarMigrating Existing REST APIs to GraphQL

Предыдущая статьяJava против Kotlin. Android
Следующая статьяПопулярные лайфхаки для Python