Я помню время, когда GraphQL стал популярным, в то время я работал руководителем команды Android и BackEnd. Тогда мы много думали об использовании GraphQL, но в итоге решили использовать REST, потому что GraphQL был действительно новым для всех, и у нас не было времени на рискованные разработки. Второй раз, когда я столкнулся с GraphQL, я столкнулся с улучшением движка CMS в Natural Intelligence, который используется и модифицируется отделами исследований и разработок компании. На этот раз выбор GraphQL был естественным решением. .
В этой статье я хочу рассказать, как мы построили инфраструктуру GraphQL и как мы используем GraphQL в производственной среде.
Во-первых, почему мы решили использовать GraphQL?
- Чрезмерная выборка: мы увидели, что при использовании REST для визуализации пользовательского интерфейса для нашего пользователя мы извлекаем много бесполезных данных, что замедляет запросы и утяжеляет все кластерные сети. , мы хотели получить только необходимые данные для пользовательского интерфейса.
- Схема: при использовании стандартного REST вы толком не знаете, что будет результатом вызова, конечно, вы можете посмотреть схему базы данных или чванство, но во многих случаях у вас есть мутации данных в качестве крючков предварительного ответа и не имеют чванства. При написании запросов GraphQL разработчик должен знать о схеме, и если что-то не существует в схеме, разработчик немедленно получит ошибку проверки.
import { gql } from '@apollo/client'; import { omitBy, isEmpty } from 'lodash'; export const query = gql` query Sites($queryParams: JSONObject, $count: Int, $offset: Int) { sites(queryParams: $queryParams, count: $count, offset: $offset){ id siteId createdAt createdByUser title statistics { isCurrentPublished wasPublished totalVariants defaultPublishedVariant { createdByUser createdAt publishAt } } } } `; export const getVariables = props => ({ queryParams: omitBy(props.query, isEmpty), count: 50, offset: 0, }); export default query;
- Параллелизм: в большинстве случаев мы наблюдали параллельные и поставленные в очередь запросы, исходящие от клиентского приложения, после ответа мы агрегировали возвращенные данные в логическую структуру пользовательского интерфейса. Мы хотели сократить процесс принятия решений на стороне клиента и перенести всю старую логику запросов на сторону сервера. Мы решили взять нестандартное решение и использовать apollo gateway. С помощью шлюза apollo теперь мы можем сделать только один вызов на стороне сервера и позволить шлюзу решить, какие микросервисы (преобразователи) вызывать для выполнения запроса, сделать его параллельным или поставленным в очередь, и агрегировать данные, используя наш первоначальный запрос.
- Безопасность схемы: мы хотели, чтобы наши схемы были безопасными и всегда имели обратную совместимость для недостаточно используемых полей. Для этого мы добавили механизм проверки аполлона на уровне PR для каждого микросервиса, и теперь, если кто-то удалил или изменил используемое поле, процесс проверки аполлона завершится ошибкой, и вы не сможете развернуть свое изменение ( если ваше изменение безопасно, администратор студии Apollo может пометить это изменение как безопасное и позволить процессу двигаться дальше). После того, как изменение помечено как безопасное, в рамках процесса развертывания мы выполняем процесс публикации apollo, который загружает новую схему в студию apollo.
- Площадка GraphQL: мы хотели, чтобы команды могли легко работать с нашими данными, не думая о REST, и легко видеть все схемы в одном месте, для этого мы интегрировали игровую площадку GraphQL как часть нашего приложения, и сделали его доступным для наших соответствующих внутренних пользователей.
Итак, как мы это реализовали?
У нас много похожих потоков, и мы полагаемся на стабильные уровни архитектуры контроллера, сервиса и репозитория (подробнее об этом можно прочитать здесь). Мы решили создать пакет, чтобы включить в него общие преобразователи, определения и построители запросов, которые будут использоваться в каждом микросервисе. Это решение может быть расширено для поддержки любого конкретного варианта использования. Идея состоит в том, что каждый строитель будет иметь внедряемый уровень службы и параметры параметров, которые будут использоваться для разрешения данных.
Пример универсальных преобразователей:
import queryParamsType from './types/queryParams.interface'; import Service from './types/service.interface'; export interface ResolverDTO { service: Service; } export interface ResolverWithParamDTO extends ResolverDTO { paramName: string; } export const getEntities = ({ service }: ResolverDTO) => async (parent: object, args: queryParamsType) => { const result = await service.getEntities(args); return result.content; }; export const getById = ({ service }: ResolverDTO) => async (parent: object, args: queryParamsType) => { const { id } = args; const result = await service.getEntity(id); return result.content; }; export const getExternalsById = ({ service, paramName }: ResolverWithParamDTO) => async (parent: object) => { const parentIdParam = parent[paramName]; const result = await service.getEntity(parentIdParam); return result.content; }; export default { getEntities, getById, getExternalsById, };
Общие компоновщики определений:
import niPluralize from '@naturalintelligence/pluralize'; import Service from './types/service.interface'; import genericResolvers from './genericResolvers'; export interface DefinitionsDTO { entityName: string; service: Service; } export const getEntities = ({ entityName, service }: DefinitionsDTO): object => ({ [`${niPluralize(entityName)}`]: genericResolvers.getEntities({ service }), }); export const getById = ({ entityName, service }: DefinitionsDTO): object => ({ [`${entityName}ById`]: genericResolvers.getById({ service }), }); export const getDefault = ({ entityName, service }: DefinitionsDTO): object => ({ ...getEntities({ entityName, service }), ...getById({ entityName, service }), }); export default = getDefault;
Реализация преобразователей в микросервисе:
const { backOffice: { resolvers: { genericResolvers, definitions }, }, } = require('@naturalintelligence/shared-graphql'); const productsService = require('../services/products-service'); const sitesService = require('../services/sites-service'); const commonEntityResolvers = { site: genericResolvers.getExternalsById({ service: sitesService, paramName: 'siteId', }), }; const resolvers = { Product: commonEntityResolvers, Query: { ...definitions.getDefault({ entityName: 'product', service: productsService }) }, }; module.exports = { resolvers, };
Общие определения типов в общем пакете:
import { upperFirst } from 'lodash'; import niPluralize from '@naturalintelligence/pluralize'; export interface QueryDTO { entityName: string; returnableType?: string; } export const getEntitiesQuery = ({ entityName, returnableType = upperFirst(entityName) }: QueryDTO): string => ` ${niPluralize( entityName )}(queryParams: JSONObject, extendedInfo: Int, count: Int, offset: Int, search: String): [${returnableType}] `; export const getByIdQuery = ({ entityName, returnableType = upperFirst(entityName) }: QueryDTO): string => ` ${entityName}ById(id: ID!): ${returnableType} `; export const getEntityDefaultQueries = ({ entityName, returnableType = upperFirst(entityName) }: QueryDTO): string => ` ${getEntitiesQuery({ entityName, returnableType })} ${getByIdQuery({ entityName, returnableType })} `; export default getEntityDefaultQueries;
Реализация Typedefs в микросервисе:
const { gql } = require('apollo-server-express'); const { backOffice: { queryTypes, typeDefs: { Statistics, Scalars }, }, } = require('@naturalintelligence/shared-graphql'); const { ProductSchema } = require('./schemas'); const typeDefs = gql` ${Scalars} ${Statistics} ${ProductSchema} extend type Query { ${queryTypes.getEntityDefaultQueries({ entityName: 'product' })} } `; module.exports = { typeDefs, };
Итак, каковы результаты?
- Размер полезной нагрузки резко уменьшился.
- Количество клиентских запросов уменьшилось.
- Скорость разработчиков увеличилась.
- Повышена безопасность схемы.
Выводы
Когда я впервые столкнулся с GraphQL, я боялся его использовать, потому что это не было стандартом, это была новая технология в то время. Но через несколько лет стало ясно, что выбор GraphQL — это способ улучшить нашу систему.
Следуя шагам, описанным в этой статье, вы сможете создать свою собственную инфраструктуру, как и я, чтобы повысить скорость разработки, помочь разработчикам добавлять новые объекты в GraphQL и повысить качество вашей системы.
«Хорошая архитектура является ключом к созданию модульных, масштабируемых, поддерживаемых и тестируемых приложений».
Большое спасибо компании Natural Intelligence, которая предоставила мне место и все ресурсы для реализации успешного проекта по улучшению.
Удачного кодирования :)