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

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

В блоге Blog by Fi1osof

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

Сегодня речь пойдет об 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

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