Fi1osof
11 янв. 2018 г., 5:00

Расширение API-запросов в ShopModxBox

Всем добрый день!

Сегодня речь пойдет об API сборки. Напомню, что сердцем обновленной сборки является API на базе GraphQL. Если кто еще не видел, можно поиграться здесь: https://shopmodx.ru/db/

Как все это работает? В GraphQL есть две основные составляющие:
1. Схема, то есть структура данных, которые в принципе могут быть запрошены. К примеру, вот схема заказа, а вот схема отдельной позиции в заказе.

Давайте разберем это на примере товарной позиции.
export const OrderProductFields = { id: { type: GraphQLInt, description: "ID", }, order_id: { type: GraphQLInt, description: "ID заказа", }, product_id: { type: GraphQLInt, description: "ID товара", }, quantity: { type: GraphQLInt, description: "Количество", }, price: { type: GraphQLFloat, description: "Стоимость", }, Product: { type: MODXResourceType, description: "Товар", resolve: (source, args, context, info) => { const { product_id, } = source; // console.log("product_id", product_id); if(!product_id){ return null; } const { rootResolver, } = context; Object.assign(args, { id: product_id, // _store: "remote", }); return rootResolver(null, args, context, info); }, }, }; const OrderProductType = new GraphQLObjectType({ name: 'OrderProductType', description: 'Позиция заказа', fields: () => (OrderProductFields), });
Большинство таких объектов - производные от класса GraphQLObjectType (хотя есть и другие классы, но мы их не будем сейчас разбирать здесь). В таких объектах два обязательных поля: name и fields. И вот тут сразу отмечу пару тонкостей. Во-первых, в рамках всей GraphQL-схемы имена объектов должны быть уникальными. Нельзя иметь в схеме два объекта с одним именем, даже если все поля в них разные. Это необходимо для правильного разбора структуры, так как граф позволяет формировать многоуровневые запросы с зависимостями и еще на уровне сбора схемы проверяет все ли везде в порядке. Плюс ко всему на основе общей структуры генерируется документация по сформированному АПИ.

Во-вторых, в качестве полей допустимо использовать не только простые поля типа GraphQLInt, GraphQLString и т.п., но и другие объекты. В данном примере это Product с типом MODXResourceType, экспортируемый из ../modResource, который, в свою очередь, экспортирует этот тип из компонента modx-react.

Да, все это довольно запутанно, но зато, как я убедился уже не раз в работе с различными проектами, потом не приходится бегать по всему проекту и искать ответы на вопросы что можно получить по АПИ, а что нет. Даже на тех проектах, где предоставлялась документация, на практике оказывалось, что документация часто не соответствует действительности (разработчик внес изменения в АПИ, а в документацию забыл). Здесь же документация генерируется на лету. А если где-то в схеме оказалась какая-то ошибка, то вы в принципе запроса не выполните, пока не устраните ошибку. И даже если у вас все хорошо, то есть валидация и по типам данных, то есть в АПИ прописан для поля тип данных число, а в ответе от источника пришла строка. Все, граф на это заругается, и правильно сделает. Так что на выходе потом имеющийся порядок компенсирует все мучения.

Здесь уточню, что хотя мы в поле прописали только одну сущность - Product, на выходе мы в этом месте получим всю структуру объекта Product.

Вторая часть графа - это уже непосредственно тело запроса. К примеру, если у вас есть аккаунт на гитхабе, вы можете поиграться с их конструктором запросов https://developer.github.com/v4/explorer/


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

query OwnOrder( $orderGetProducts:Boolean = false $orderProductGetProduct:Boolean = false $getImageFormats:Boolean = false ){ order( ownOrder: true ) @storage(store:remote) { ...Order } } fragment Order on OrderType{ ...OrderFields Products @include(if:$orderGetProducts) @storage(store:remote) { ...OrderProduct } }
query OwnOrder - это название отдельной операции.
$orderGetProducts:Boolean = false и т.п. - это входящие переменные (с указанием типа данных и значения по умолчанию).

Это непосредственно тело запроса:
order( ownOrder: true ) @storage(store:remote) { ...Order }
...Order - это конструкция передачи вывода в заранее определенный фрагмент (выше его код так же приведен).

@storage - директива, но директивы мы рассмотрим как-нибудь позже.

Так вот, все эти запросы описаны заранее и обрабатываются на стороне сервер, то есть передавая на сервер запрос OwnOrder, граф находит эту операцию в подготовленном листинге запросов и собирает указанные данные. Вот весь этот листинг в компоненте shopmodx-react: https://github.com/MODX-Club/shopmodx-react/blob/master/components/ORM/query.js

Но тут встает вопрос: а как же на конечном проекте расширять эти запросы, если они уже заранее прописаны в обновляемом компоненте?

Для решения этой задачи я выпустил сегодня новый компонент react-cms-graphql-utils, в составе которого имеется полезная функция mergeQuery https://github.com/MODX-Club/react-cms-graphql-utils/blob/master/src/mergeQuery.js

Позже я покажу реальные кейсы с применением всего этого.




Кстати, с GraphQL можно и здесь поиграться: https://modxclub.ru/react-lessons/lesson2
Отдельный урок про GraphQL был здесь: https://modxclub.ru/topics/react-js.-urok-%E2%84%962.-zaprosyi-s-pomoshhyu-graphql-2693.html

Добавить комментарий