Создаем свое modx.pro API на GraphQL
Всем привет!
Поддержу возникший марафон и напишу свою заметку в раздел «Для разработчиков». Думаю, многим понравится, тем более что тестовые примеры будут касаться в том числе и самого сайта modx.pro. И хотя будет много буков написано и самое основное не сразу будет показано, все-таки попрошу набраться терпения и если вы не сильны в GraphQL, то прочитать внимательно все с самого начала и по возможности повторить тоже самое самостоятельно. Просто я буду описывать практически с самых основ, и хотя во многом все покажется и окажется просто, данный материал будет очень полезен для дальнейшего самостоятельного освоения GraphQL.
Итак, быстрый старт. Вот мой демо-интерфейс: s19387.h10.modhost.pro:4123/
Можете сразу вставить вот такой листинг запросов:
Ну как? Попробовали выполнить запросы? Если да и готовы продолжать дальше, то давайте разберем уже более внимательно что и как тут работает. Перейдем непосредственно к установке.
Для начала по хостингу: методом проб и ошибок я установил, что modhost.pro/ в общих чертах подходит для экспериментов, хотя на тестовом тарифном плане не получится (хотя в официальной тарифной сетке указано 250 метров диска будет доступно, на практике чуть больше ста, и запись на диск прерывалась (80 метров в кеш npm и финита ля комедия)). Но на минимальном тарифном плане за 75 рэ в месяц все полетело нормально. Хотя предполагаю, что эта лавочка может прикрыться, потому что нода сама по себе требует много ресурсов (та же установка всех зависимостей требует порядка гигабайта оперативки, и это в среднем в течение минуты). На сервере, который мне выделился, сейчас 10 Гб оперативы свободно. Если 10 человек одновременно запустит установку зависимостей, сервер заплачет. То есть хотя на php-процессы и есть лимиты, на нод-процессы я лимитов не увидел. Так что у кого есть возможность тестировать на своем железе, просьба: лучше там (хоть локально). Но если альтернативы нет, то наверно можно и там.
1. Заходим по ssh на сервер. создаем себе где-нибудь папку для проекта и клонируем туда мой репозиторий.
Все. Если все ОК, то открываете адрес вашего сайта/сервера с указанием своего порта. У меня порт 4123, соответственно адрес s19387.h10.modhost.pro:4123/
5. Запускаем API-сервер постоянный процесс.
Если вы закроете терминал, в котором запускали сервер, то и сервер завершит свою работу. Поэтому, если мы хотим, чтобы сервер работал постоянно (а в случае, если развалится по какой-то причине, чтобы автоматически перезапустился), воспользуемся дополнительным инструментом — pm2 (есть аналог forever, но он мне не нравится по ряду причин, хотя начал его использовать раньше).
Вообще pm2 надо устанавливать глобально командой
Выполняем
[PM2] Starting /usr/bin/npm in fork_mode (1 instance)
[PM2] Done.
┌───────────────────┬────┬─────────┬──────┬───────┬────────┬─────────┬────────┬─────┬───────────┬────────┬──────────┐
│ App name │ id │ version │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │
├───────────────────┼────┼─────────┼──────┼───────┼────────┼─────────┼────────┼─────┼───────────┼────────┼──────────┤
│ modx.pro-api:4123 │ 0 │ N/A │ fork │ 19495 │ online │ 0 │ 0s │ 0% │ 28.4 MB │ s19387 │ disabled │
└───────────────────┴────┴─────────┴──────┴───────┴────────┴─────────┴────────┴─────┴───────────┴────────┴──────────┘
В дальнейшем можно посмотреть логи процесса
Если служба работает успешно, можно закрывать терминал.
Разберем API-схему.
Вот здесь описана схема для Query: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/schema/api/schema.graphql
Вот здесь для Mutation: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/schema/api/mutation.graphql
Когда мы выполняем npm run build-api, скрипт собирает все схемы в модуле и объединяет их в конечные сземы, используемые API-сервером. Они в итоге попадают вот сюда: github.com/Fi1osof/api.modx.pro/tree/master/src/schema/generated (эти схемы не должны отслеживаться гитом). То есть мы может несколько раз описать одни и те же объекты (как, к примеру, в нашей схеме дважды описан type Query, просто с разными полями), и можем даже их описать в разных файлах, но когда мы билдим api-схему, у нас происходит объединение деклараций, и если конфликтов не будет, то они соберутся в конечную схему с уникальными типами.
Резолверы
Итак, схему мы собрали и уже можно запускать сервер, и если со схемой все ОК, то он запустится. Но для реальной обработки запросов нужны еще функции-резолверы. Поясню: схема определяет только какие запросы можно отправлять, какие параметры в них и какие результаты вы можете получить, но схема не выполняет конечных операций (запросов, арифметических действий и т.п.), для этого нужны непосредственно функции. При этом в обратную сторону это не работает. То есть если вы создали функцию-резолвер, но не описали ее в схеме, то сервер не запустится. Он скажет, к примеру "[Error: Query.test_resolver defined in resolvers, but not in schema]", то есть «резолвер есть, а схемы нет».
Вот наши резолверы: github.com/Fi1osof/api.modx.pro/blob/3b56bff42fd181162561562eea5fd5bb538698e0/src/modules/index.mjs#L89-L114
Кто с JS хоть чуть-чуть дружит, наверняка разберет что происходит. Рассмотрим на примере функции calc_sum.
source — родительских объект. Если используется многоуровневый запрос, запрашивающий еще и вложенные данные, на каждом уровне выполнения можно будет получить родительский объект. Но на первом уровне он как правило всегда undefined. Мы этот параметр более внимательно рассмотрим ниже, у нас будет пример многоуровневого запроса.
args — входящие параметры. К примеру, вот наш вызов:
При чем обратите внимание на то, что я даже не делаю проверку наличия этого аргумента:
К слову, еще немного о преобразовании. В схеме у нас описано, что foo_number возвращает число.
1. Если наш метод попытается вернуть дробное число, мы получим ошибку, а не число.
2. Даже если мы возвращаем строку, которая может корректно быть преобразована в число, то мы получим в ответе именно число без всяких ошибок. Пример результата выполнения:
Ну а теперь переходим к самому интересному — созданию своего API для modx.pro
Конечно же у нас нет прямого доступа к базе данных modx.pro и мы не сможем получить больше, чем даст нам сам сайт, но тем не менее для эксперимента у нас есть парочка подопытных источников данных:
1. Список пользователей на самом modx.pro
2. Список авторов на modstore.pro
Для этих страниц данные подгружаются по API и их-то мы и возьмем.
Вот я прописал схемы для пользователей самого modx.pro (ModxProUser) и авторов компонентов (ModxProAuthor): github.com/Fi1osof/api.modx.pro/tree/master/src/modules/schema/api/modx.pro
Вот пример запроса пользователей с modx.pro:
Вот модуль пользователя: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProUser/index.mjs
Вот здесь мы выполняем запрос на modx.pro и получаем данные: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProUser/index.mjs#L36-L43
А вот это модуль автора: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs
В целом здесь все то же самое, вот здесь мы запрашиваем данные: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs#L36-L43
Но самое интересное находится вот здесь: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs#L119-L182
Если смотреть схему, то мы видим, что запрос modx_pro_authors_connection возвращает объект ModxProAuthorsConnection.
Уточню, что если это поле не было запрошего, то оно и не выполняется.
И здесь я пытаюсь понять, был ли у автора запрошен объект User: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs#L133-L136
Если да, то я запрашиваю список всех пользователей с modx.pro и пытаюсь для полученных авторов найти их пользователей: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs#L139-L175
Уточню два момента:
1. Все пользователи с modx.pro запрашиваются только в том случае, если для автора был прописан подзапрос пользователя.
2. Хотя мы получаем массив авторов, мы не выполняем столько же запросов на modx.pro за всеми пользователями, сколько было получено авторов. Нет, мы обрабатываем только одно поле results, содержащее массив полученных авторов. И запрос на modx.pro мы выполняем соответственно один. Просто перебираем потом массивы и все.
К сожалению, я в этих двух массивах пользователей не сумел обнаружить признаки, которые бы четко идентифицировали авторов и пользователей, поэтому некоторые авторы не содержат данные пользователей.
Еще немного о валидации
Вот чем мне нравится GraphQL, так это тем, что он очень жесткий по части входных и выходных данных. Помню в свое время разрабатывал промежуточный API-сервер на одном важном проекте. ТАм я должен был получать данные от API-сервера заказчика и выводить уже в конечную верстку. Так вот, возвращаемые данные от их API-сервера часто не соответствовали описанию в документации. То есть есть документация, но проект активно развивается, что-то меняется, а документацию не успевают оперативно поправлять. В классическом варианте я шлю API-запрос и получаю ответ, рассчитывая, что ответ придет в соответствии с документацией. Но возникают случаи, когда в большинстве ответов поля эти имеются и данные соответствуют, но для каких-то отдельных возвращаемых сущностей данные не соответствуют. И хорошо если сразу увидел. А если нет? Не можешь же все перепроверить. И вот когда я API-схему прописал и начал использовать промежуточный сервер на графе, вот тогда-то и посыпались ошибки «Здесь значение не было получено, хотя оно обязательно», «Здесь тип данных не тот», «Здесь значение не соответствует заявленному списку возможных значений enum» и т.п.
Вот, собственно, примерно это я и здесь буду сейчас это проходить, и по ходовой сразу буду фиксировать, с чем сталкивался.
Разные ответы в случае ошибки и успеха.
Посмотрел на сайте ответ сервера и по нему схему описал первоначально:
Ладно, дописал схему:
В схеме для данных постраничности я прописал:
Булевое значение work.
Для поля work от сервера приходит «0» или «1». Но если я просто пропишу Int, то можно рассчитывать на значения 2, 3 и т.п. А у нас только 0 или 1. Соответственно а схеме я прописал Boolean. Но при выполнении я получаю:
Что в процессе удалось выяснить?
1. Можно запросить и получить сразу весь список пользователей, передав limit: 0;
Не вижу проблемы, а наоборот, это хорошо, потому что можно разом получить сразу всех пользователей. Но вот получение полного списка всех статей было бы напряжно)).
2. Нельзя искать пользователей без рейтинга, можно только с рейтингом.
То есть если передать rating: true, то даст список пользователей только с положительным рейтингом. Но если передать rating: false, то он возвращает просто всех пользователей, а не только тех, у кого рейтинг отрицательный, то есть этот параметр игнорируется. В итоге убрал этот параметр пока из условий поиска, добавил параметр positive_rating_only: Boolean. Так логичней. То есть если «только с положительным» === true, тут понятно. Если «только с положительным» === false, тут тоже понятно, потому что «дай нам не только с положительным». Тонкости…
К слову, я вообще по себе заметил, что работая с JS я стал более внимательным к типам данных и условиям. К примеру, раньше я тоже в подобных случаях на php писал
Собственно, то же касается и условия work. Нельзя получить только тех, кто не принимает заказы. Поменял на work_only
3. Сортировку нельзя указать по любому полю, только одно из перечисленных. Подсмотрел три варианта, больше не увидел (даже возможности по fullname отсортировать). Поэтому перечислил четко варианты:
В списке авторов дополнений
4. В списке авторов дополнений id — это не классический цифровой id, а username.
5. Аватары адреса не совпадают.
В обычном списке «avatar»: "/assets/images/avatars/3508/c3ddc31708cbdb38dee68bbd8babb8ca-48.jpg?t=1551334248"
а в списке авторов avatar: «3a1f53ab0678a39c3aad9f8669ad9f3a»
6. В списке авторов fullname, как положено, а в обычном списке это name.
7. modstore.pro и modx.pro судя по всему — это абсолютно разные сайты со своими отдельными базами данных.
8. Не увидел фильтра авторов по тегам.
Заключение.
Если кто-то проявил интерес к вот этой теме, то, думаю, организация API под это будет в кассу, ведь API — необходимая часть JAM-стэка.
P.S. Я надеюсь каждый для себя сделает выводы, интересна такая технология организации API или нет. Но лично я после знакомства с GraphQL на обычный REST не вернусь уже ни за что.
Еще раз ссылка на исходники: github.com/Fi1osof/api.modx.pro
Поддержу возникший марафон и напишу свою заметку в раздел «Для разработчиков». Думаю, многим понравится, тем более что тестовые примеры будут касаться в том числе и самого сайта modx.pro. И хотя будет много буков написано и самое основное не сразу будет показано, все-таки попрошу набраться терпения и если вы не сильны в GraphQL, то прочитать внимательно все с самого начала и по возможности повторить тоже самое самостоятельно. Просто я буду описывать практически с самых основ, и хотя во многом все покажется и окажется просто, данный материал будет очень полезен для дальнейшего самостоятельного освоения GraphQL.
Итак, быстрый старт. Вот мой демо-интерфейс: s19387.h10.modhost.pro:4123/
Можете сразу вставить вот такой листинг запросов:
query foo_string{
foo_string
}
query foo_number{
foo_number
}
query foo_json{
foo_json
}
mutation calc_sum{
calc_sum (
vars: [2,4, 34.5, 5]
)
}Это три запроса «на чтение» (Query) и один на «изменение» (Mutation). На самом деле «чтение» и «изменение» в graphql — весьма условно, потому что реально решаться что будет просто прочтено, а что изменено — это уже будет на уровне функций-резолверов, о которых сам graphql мало что знает и его не сильно касается что там будет происходить (просто чтение или запись), но все-таки принято разделять эти типы операций, тем более что можно отдельно указать адрес для выполнения Query-запросов, и отдельно для Mutation. И вроде как там какая-то особенность с очередностью выполнения запросов при передаче сразу нескольких подзапросов внутри одного, например так:query all_query{Вроде как Query могут как попало по очередности выполниться, а Mutation строго последовательно, как перечислено в запросе. Но это не точно и не особо суть, так, на заметку.
foo_string
foo_number
foo_json
}
Ну как? Попробовали выполнить запросы? Если да и готовы продолжать дальше, то давайте разберем уже более внимательно что и как тут работает. Перейдем непосредственно к установке.
Для начала по хостингу: методом проб и ошибок я установил, что modhost.pro/ в общих чертах подходит для экспериментов, хотя на тестовом тарифном плане не получится (хотя в официальной тарифной сетке указано 250 метров диска будет доступно, на практике чуть больше ста, и запись на диск прерывалась (80 метров в кеш npm и финита ля комедия)). Но на минимальном тарифном плане за 75 рэ в месяц все полетело нормально. Хотя предполагаю, что эта лавочка может прикрыться, потому что нода сама по себе требует много ресурсов (та же установка всех зависимостей требует порядка гигабайта оперативки, и это в среднем в течение минуты). На сервере, который мне выделился, сейчас 10 Гб оперативы свободно. Если 10 человек одновременно запустит установку зависимостей, сервер заплачет. То есть хотя на php-процессы и есть лимиты, на нод-процессы я лимитов не увидел. Так что у кого есть возможность тестировать на своем железе, просьба: лучше там (хоть локально). Но если альтернативы нет, то наверно можно и там.
1. Заходим по ssh на сервер. создаем себе где-нибудь папку для проекта и клонируем туда мой репозиторий.
git clone https://github.com/Fi1osof/api.modx.pro2. Переходим в папку скаченного репозитория и выполняем установку зависимостей.npm i3. Выполняем сборку API-схемы.npm run build-api4. Запускаем API-сервер.PORT=4xxx npm run startВот тут уточнение: есть предположение, что порты на сервере для всех едины (там же вроде как просто разграничение прав по пользователям идет, а не просто каждому своя виртуальная машина), так что если порт кем-то занят уже, вы не сможете на нем запуститься. Поэтому прописывайте какой-нить рандомный порт в диапазоне 4000-4999, например 4156, 4589 и т.п. То есть если пытаетесь запуститься и получаете ошибку занятости порта, просто попробуйте другой.Все. Если все ОК, то открываете адрес вашего сайта/сервера с указанием своего порта. У меня порт 4123, соответственно адрес s19387.h10.modhost.pro:4123/
5. Запускаем API-сервер постоянный процесс.
Если вы закроете терминал, в котором запускали сервер, то и сервер завершит свою работу. Поэтому, если мы хотим, чтобы сервер работал постоянно (а в случае, если развалится по какой-то причине, чтобы автоматически перезапустился), воспользуемся дополнительным инструментом — pm2 (есть аналог forever, но он мне не нравится по ряду причин, хотя начал его использовать раньше).
Вообще pm2 надо устанавливать глобально командой
sudo npm i -g pm2но на модхост у нас на это нет прав, поэтому устанавливаем его прям в текущем проекте.npm i pm2Если бы мы установили его глобально, тогда можно было бы выполнять как обычную команду, вызывая pm2 [args]. Но ничего, так мы будем просто полный путь к исполняемому файлу указывать и все. Выполняем
PORT=4xxx ./node_modules/.bin/pm2 --name modx.pro-api:4123 start npm -- run startВ результате получите типа такого:[PM2] Starting /usr/bin/npm in fork_mode (1 instance)
[PM2] Done.
┌───────────────────┬────┬─────────┬──────┬───────┬────────┬─────────┬────────┬─────┬───────────┬────────┬──────────┐
│ App name │ id │ version │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │
├───────────────────┼────┼─────────┼──────┼───────┼────────┼─────────┼────────┼─────┼───────────┼────────┼──────────┤
│ modx.pro-api:4123 │ 0 │ N/A │ fork │ 19495 │ online │ 0 │ 0s │ 0% │ 28.4 MB │ s19387 │ disabled │
└───────────────────┴────┴─────────┴──────┴───────┴────────┴─────────┴────────┴─────┴───────────┴────────┴──────────┘
В дальнейшем можно посмотреть логи процесса
pm2 log [id]Посмотреть список всех процессовpm2 lsПерезагрузить процесс pm2 reload idИ т.п. Смотрите официальную документацию.Если служба работает успешно, можно закрывать терминал.
Разберем API-схему.
Вот здесь описана схема для Query: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/schema/api/schema.graphql
Вот здесь для Mutation: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/schema/api/mutation.graphql
Когда мы выполняем npm run build-api, скрипт собирает все схемы в модуле и объединяет их в конечные сземы, используемые API-сервером. Они в итоге попадают вот сюда: github.com/Fi1osof/api.modx.pro/tree/master/src/schema/generated (эти схемы не должны отслеживаться гитом). То есть мы может несколько раз описать одни и те же объекты (как, к примеру, в нашей схеме дважды описан type Query, просто с разными полями), и можем даже их описать в разных файлах, но когда мы билдим api-схему, у нас происходит объединение деклараций, и если конфликтов не будет, то они соберутся в конечную схему с уникальными типами.
Резолверы
Итак, схему мы собрали и уже можно запускать сервер, и если со схемой все ОК, то он запустится. Но для реальной обработки запросов нужны еще функции-резолверы. Поясню: схема определяет только какие запросы можно отправлять, какие параметры в них и какие результаты вы можете получить, но схема не выполняет конечных операций (запросов, арифметических действий и т.п.), для этого нужны непосредственно функции. При этом в обратную сторону это не работает. То есть если вы создали функцию-резолвер, но не описали ее в схеме, то сервер не запустится. Он скажет, к примеру "[Error: Query.test_resolver defined in resolvers, but not in schema]", то есть «резолвер есть, а схемы нет».
Вот наши резолверы: github.com/Fi1osof/api.modx.pro/blob/3b56bff42fd181162561562eea5fd5bb538698e0/src/modules/index.mjs#L89-L114
Кто с JS хоть чуть-чуть дружит, наверняка разберет что происходит. Рассмотрим на примере функции calc_sum.
calc_sum(source, args, ctx, info) {
const {
vars,
} = args;
if (vars.length < 2) {
throw new Error("Необходимо минимум два числа");
}
return vars.reduce((a, b) => a + b);
}
}Все резолверы имеют 4 входящие параметра:source — родительских объект. Если используется многоуровневый запрос, запрашивающий еще и вложенные данные, на каждом уровне выполнения можно будет получить родительский объект. Но на первом уровне он как правило всегда undefined. Мы этот параметр более внимательно рассмотрим ниже, у нас будет пример многоуровневого запроса.
args — входящие параметры. К примеру, вот наш вызов:
mutation calc_sum{
calc_sum (
vars: [2,4, 34.5, 5]
)
}Внутри функции мы получим args.vars.При чем обратите внимание на то, что я даже не делаю проверку наличия этого аргумента:
if (vars.length < 2) {
throw new Error("Необходимо минимум два числа");
}Дело в том, что в схеме я описал, что этот аргумент обязательный.type Mutation {
"""Return sum of inputs"""
calc_sum(
vars: [Float!]!
): Float!
}Знак! — это признак «Обязательно». То есть в данном случае [Float!] — это массив дробных чисел, а [Float!]! — это обязательный массив. Но Float! не требует наличия хотя бы одного элемента (такая вот особенность), так что может прийти пустой массив. Но если в этом массиве какие-то значения будут перечислены, это обязательно будут числа (схема к этому обяжет). По этой причине я и не делаю проверку типов данных при суммировании их.return vars.reduce((a, b) => a + b);Наверняка многие знают, что «12» + 13 это будет строковое «1213», а не число 25. Здесь схема не допустит передачи строки вместо числа.К слову, еще немного о преобразовании. В схеме у нас описано, что foo_number возвращает число.
foo_number: Int!При этом прописано Int, а не Float, то есть целочисленное, а не дробное. По этой причине:1. Если наш метод попытается вернуть дробное число, мы получим ошибку, а не число.
2. Даже если мы возвращаем строку, которая может корректно быть преобразована в число, то мы получим в ответе именно число без всяких ошибок. Пример результата выполнения:
{
"data": {
"foo_number": 263
}
}При этом я сознательно оставил в резолвере return (Math.random() * 1000).toFixed(); Если кто в курсе, Number.toFixed() возвращает строчное значение, а не число. В подтверждение этого посмотрим метод foo_json, в котором я прописал одно из значений — результат выполнения метода this.foo_number(). Вот пример ответа:{
"data": {
"foo_json": {
"date": {
"date": "2019-08-08T01:04:41.095Z",
"year": 2019,
"month": 7,
"day": 4
},
"foo_number": "47"
}
}
}Результат foo_number в данном случае у нас строчное «47». Дело в том, что foo_json у нас возвращает json. GraphQL не делает проверки типов внутренних значений для него, он просто проверяет, что возвращаемый результат действительно JSON-объект. При этом если мы попытаемся вернуть число или строку, получим ошибку.Ну а теперь переходим к самому интересному — созданию своего API для modx.pro
Конечно же у нас нет прямого доступа к базе данных modx.pro и мы не сможем получить больше, чем даст нам сам сайт, но тем не менее для эксперимента у нас есть парочка подопытных источников данных:
1. Список пользователей на самом modx.pro
2. Список авторов на modstore.pro
Для этих страниц данные подгружаются по API и их-то мы и возьмем.
Вот я прописал схемы для пользователей самого modx.pro (ModxProUser) и авторов компонентов (ModxProAuthor): github.com/Fi1osof/api.modx.pro/tree/master/src/modules/schema/api/modx.pro
Вот пример запроса пользователей с modx.pro:
query modx_pro_users{
modx_pro_users_connection(
limit: 10
where:{
query: ""
work_only: true
positive_rating_only: false
}
orderBy:{
sort: topics
dir: desc
}
){
success
message
data
limit
start
total
results{
...user
}
pages{
...page
}
}
}
fragment page on ModxProUsersConnectionPage{
isActive
isCurrent
num
url
}
fragment user on ModxProUser {
id
avatar
avatar_retina
comments
idx
link
name
place
rating
topics
username
visitedon
work
}А вот авторов с modstore.pro:query modx_pro_authors{
modx_pro_authors_connection(
limit: 2
where:{
query: ""
work_only: true
positive_rating_only: false
}
orderBy:{
sort: place
dir: asc
}
){
success
message
data
total
results{
...user
User{
id
username
avatar
}
}
}
}
fragment user on ModxProAuthor {
id
community
downloads
extras
fullname
github
place
rating
support
work
tags{
color
tag
}
}И вот обратите внимание, что во втором случае помимо данных автора, запрашиваются данные самого пользователя: User{
id
username
avatar
}Это, вероятно, самая интересная часть нашего сегодняшнего материала. Давайте внимательней разберем сами резолверы.Вот модуль пользователя: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProUser/index.mjs
Вот здесь мы выполняем запрос на modx.pro и получаем данные: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProUser/index.mjs#L36-L43
А вот это модуль автора: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs
В целом здесь все то же самое, вот здесь мы запрашиваем данные: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs#L36-L43
Но самое интересное находится вот здесь: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs#L119-L182
Если смотреть схему, то мы видим, что запрос modx_pro_authors_connection возвращает объект ModxProAuthorsConnection.
modx_pro_authors_connection(
limit: Int = 20
page: Int
where: ModxProAuthorWhereInput
orderBy: ModxProAuthorOrderByInput
): ModxProAuthorsConnection!Но, хотя мы получаем и возвращаем целиком ответ от сайта-источника, GraphQL устроен так, что если прописаны резолверы на любую часть объекта, то он прогоняет данные через него. То есть мы, в резолверы добавилиModxProAuthorsConnection: {
results: async (source, args, ctx, info) => {
....
}
}и когда GraphQL обрабатывает полученный результат ModxProAuthorsConnection, если в нем запрошего поле results, он прогоняет его через наш резолвер.Уточню, что если это поле не было запрошего, то оно и не выполняется.
И здесь я пытаюсь понять, был ли у автора запрошен объект User: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs#L133-L136
Если да, то я запрашиваю список всех пользователей с modx.pro и пытаюсь для полученных авторов найти их пользователей: github.com/Fi1osof/api.modx.pro/blob/master/src/modules/ModxProAuthor/index.mjs#L139-L175
Уточню два момента:
1. Все пользователи с modx.pro запрашиваются только в том случае, если для автора был прописан подзапрос пользователя.
2. Хотя мы получаем массив авторов, мы не выполняем столько же запросов на modx.pro за всеми пользователями, сколько было получено авторов. Нет, мы обрабатываем только одно поле results, содержащее массив полученных авторов. И запрос на modx.pro мы выполняем соответственно один. Просто перебираем потом массивы и все.
К сожалению, я в этих двух массивах пользователей не сумел обнаружить признаки, которые бы четко идентифицировали авторов и пользователей, поэтому некоторые авторы не содержат данные пользователей.
Еще немного о валидации
Вот чем мне нравится GraphQL, так это тем, что он очень жесткий по части входных и выходных данных. Помню в свое время разрабатывал промежуточный API-сервер на одном важном проекте. ТАм я должен был получать данные от API-сервера заказчика и выводить уже в конечную верстку. Так вот, возвращаемые данные от их API-сервера часто не соответствовали описанию в документации. То есть есть документация, но проект активно развивается, что-то меняется, а документацию не успевают оперативно поправлять. В классическом варианте я шлю API-запрос и получаю ответ, рассчитывая, что ответ придет в соответствии с документацией. Но возникают случаи, когда в большинстве ответов поля эти имеются и данные соответствуют, но для каких-то отдельных возвращаемых сущностей данные не соответствуют. И хорошо если сразу увидел. А если нет? Не можешь же все перепроверить. И вот когда я API-схему прописал и начал использовать промежуточный сервер на графе, вот тогда-то и посыпались ошибки «Здесь значение не было получено, хотя оно обязательно», «Здесь тип данных не тот», «Здесь значение не соответствует заявленному списку возможных значений enum» и т.п.
Вот, собственно, примерно это я и здесь буду сейчас это проходить, и по ходовой сразу буду фиксировать, с чем сталкивался.
Разные ответы в случае ошибки и успеха.
Посмотрел на сайте ответ сервера и по нему схему описал первоначально:
type ModxProUsersConnection {
success: Boolean!
limit: Int!
start: Int
total: Int!
pages: [ModxProUsersConnectionPage!]!
results: [ModxProUser!]!
}В целом логично: success — обязательный, страница, список пользователей и т.п. тоже. Вроде ОК. Но выполнил запрос и получил результат:{
"data": null,
"errors": [
{
"message": "Cannot return null for non-nullable field ModxProUsersConnection.limit.",
"locations": [
{
"line": 4,
"column": 5
}
],
"path": [
"modx_pro_users_connection",
"limit"
]
}
]
}Оказывается, если что-то не так, ответ получаем такой:{
"success": false,
"message": "Access denied",
"data": []
}Я уже где-то писал про то, что MODX возвращает разные форматы ответа в разных случаях (в том числе в зависимости от того, как именно ты не авторизован))). Вот это тот случай.Ладно, дописал схему:
type ModxProUsersConnection {
success: Boolean!
limit: Int!
start: Int
total: Int!
pages: [ModxProUsersConnectionPage!]!
results: [ModxProUser!]!
"""Возвращается в случае ошибки"""
message: String
data: Json
}И в резолвере дописал значения по умолчанию. ОК, полетело. Теперь получаем ответ:{Ошибся с форматом поля num.
«data»: {
«modx_pro_users_connection»: {
«success»: false,
«message»: «Access denied»,
«data»: [],
«limit»: 0,
«start»: null,
«total»: 0,
«pages»: [],
«results»: []
}
}
}
В схеме для данных постраничности я прописал:
type ModxProUsersConnectionPage {
isActive: Boolean!
isCurrent: Boolean!
num: Int!
url: String!
}Думал, что num — это всегда число. И получил ошибку:{
"data": null,
"errors": [
{
"message": "Int cannot represent non-integer value: \"...\"",
"locations": [
{
"line": 21,
"column": 3
}
],
"path": [
"modx_pro_users_connection",
"pages",
9,
"num"
]
}
]
}В сообщении мы видим помимо сообщения информацию о том, в каком именно месте запроса возникла ошибка (номер строки и позиция, а так же название проблемного поля). В нашем случае это num. Смотрю ответ, а там:{
"num": "...",
"url": "&limit=20",
"isCurrent": false,
"isActive": true
},То есть в большинстве случаев там действительно число, но все-таки это немного не то, а просто титл для кнопки. Поэтому меняем тип с Int! на String!..Булевое значение work.
Для поля work от сервера приходит «0» или «1». Но если я просто пропишу Int, то можно рассчитывать на значения 2, 3 и т.п. А у нас только 0 или 1. Соответственно а схеме я прописал Boolean. Но при выполнении я получаю:
"errors": [
{
"message": "Boolean cannot represent a non boolean value: \"0\"",
"locations": [
{
"line": 38,
"column": 3
}
],
"path": [
"modx_pro_users_connection",
"results",
0,
"work"
]
}
]
}То есть graphql не хочет просто так преобразовывать «0» в false и «1» в true. Ок, допишу преобразование на уровне резолвера.Что в процессе удалось выяснить?
1. Можно запросить и получить сразу весь список пользователей, передав limit: 0;
Не вижу проблемы, а наоборот, это хорошо, потому что можно разом получить сразу всех пользователей. Но вот получение полного списка всех статей было бы напряжно)).
2. Нельзя искать пользователей без рейтинга, можно только с рейтингом.
То есть если передать rating: true, то даст список пользователей только с положительным рейтингом. Но если передать rating: false, то он возвращает просто всех пользователей, а не только тех, у кого рейтинг отрицательный, то есть этот параметр игнорируется. В итоге убрал этот параметр пока из условий поиска, добавил параметр positive_rating_only: Boolean. Так логичней. То есть если «только с положительным» === true, тут понятно. Если «только с положительным» === false, тут тоже понятно, потому что «дай нам не только с положительным». Тонкости…
К слову, я вообще по себе заметил, что работая с JS я стал более внимательным к типам данных и условиям. К примеру, раньше я тоже в подобных случаях на php писал
if($positive){
$where['positive:>'] = 0;
}но не писалif($positive === true){
$where['positive:>'] = 0;
}
else if($positive === false){
$where['positive:<='] = 0;
}А ведь разница существенная. Как результат: часть логики утеряна.Собственно, то же касается и условия work. Нельзя получить только тех, кто не принимает заказы. Поменял на work_only
3. Сортировку нельзя указать по любому полю, только одно из перечисленных. Подсмотрел три варианта, больше не увидел (даже возможности по fullname отсортировать). Поэтому перечислил четко варианты:
"""Сортировка пльзователей"""
enum ModxProUsersSort {
"""По ретингу"""
rating
"""По количеству топиков"""
topics
"""По количеству комментариев"""
comments
}В списке авторов дополнений
4. В списке авторов дополнений id — это не классический цифровой id, а username.
5. Аватары адреса не совпадают.
В обычном списке «avatar»: "/assets/images/avatars/3508/c3ddc31708cbdb38dee68bbd8babb8ca-48.jpg?t=1551334248"
а в списке авторов avatar: «3a1f53ab0678a39c3aad9f8669ad9f3a»
6. В списке авторов fullname, как положено, а в обычном списке это name.
7. modstore.pro и modx.pro судя по всему — это абсолютно разные сайты со своими отдельными базами данных.
8. Не увидел фильтра авторов по тегам.
Заключение.
Если кто-то проявил интерес к вот этой теме, то, думаю, организация API под это будет в кассу, ведь API — необходимая часть JAM-стэка.
P.S. Я надеюсь каждый для себя сделает выводы, интересна такая технология организации API или нет. Но лично я после знакомства с GraphQL на обычный REST не вернусь уже ни за что.
Еще раз ссылка на исходники: github.com/Fi1osof/api.modx.pro
Комментарии: 82
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
Fi1osof— Если тупо всю необходимую инфу получать и выводить о товарах, то RESTful подойдёт, он же проще ставится и работает только на PHP, на котором и сам MODX крутится.
— Если же нужна универсальность API, для какого-то мобильного приложения или SPA, то GraphQL будет предпочтительнее. Правда работы изначально придётся провести довольно много, чтобы прописать всё взаимодействие (запросы, мутации, подписки) в резолверы.
Fi1osofТут вопрос спорный. Уже довольно много средств есть для генерации всего и вся, в том числе резолверов. К примеру, на той же призме, достаточно описать схему и запустить деплой, будет не только структура БД создана, но и сгенерированы CRUD-резолверы, включая условия поиска, сортировки и т.п.
То есть, на примере ИМ, можно просто описать схему запросов и мутаций для msCategory, msProduct, msOrder и т.п. и оно поедет?
Fi1osofВ данном случае нет, потому что ИМ не на призме. Но если поискать, то можно наверняка что-нибудь найти для этого (но при условии наличия прямого доступа к БД, а не как я тут просто тяну POST-запросами). Вот вроде как пример: github.com/overblog/GraphQLPhpGenerator
Вот это неоспоримый факт! Я порой офигеваю от того, что я делал раньше на PHP. Жжжуть…
Так и есть, Василий ни раз об этом говорил.
Fi1osofГовориться, что на JS приходится более внимательно работать с типами и условиями. Т.е. он менее требователен и типизирован. Что в дальнейшем может приводить к ошибкам. И следом идет
Т.е. всё наоборот.
Поэтому и просил уточнить.
П.С. А TS — это typescript?
Fi1osofК примеру, в php + — это всегда суммирование, а для объединения строк используется точка («st».«ring»).
А в JS и для того, и для другого используется +. В итоге в php «1» + 1 = 2, а в js «1» + 1 = «11».
Ну а больше всего мне нравится вот этот мемас))
Объяснение: www.freecodecamp.org/news/explaining-the-best-javascript-meme-i-have-ever-seen/
Или вот такой пример: ['1', '7', '11'].map(parseInt) возвращает [1, NaN, 3].
habr.com/ru/post/456344/
Или вот: (![] +[])[+!![]] = 'а'
В общем, если в JS будешь невнимательным к типам, просто не сможешь программировать нормально.
Fi1osofFi1osofЭто говорит о том, что в принципе изначально особо и не заморачивались на счет жесткости сравнения. И я когда на php программировал, в основном только и использовал if($val) да if(!$val), то есть просто простейшие сравнения использовал.
В js я использую:
1. foo === undefined чтобы проверить была ли вообще передана переменная (в php аналог isset).
2. foo === null, чтобы проверить, что переменная была передана, но значение установлено нулевое, то есть мне именно надо сбросить значение.
Говоря про эти два момента, в php обычно просто проверяют isset() и empty(), но если брать $foo = null, то isset($foo) и empty($foo) оба вернут true. Часто ли ты используешь сравнение с null?
При этом смотри какой интересный момент:
Получаем «PHP notice: Undefined variable: v», но «11», то есть необъявленная переменная таки === null.
В js undefuned !== null, то есть для меня это дополнительные условия сравнения.
3. instanceof.
4. typeof.
3 и 4 аналоги тоже есть в php, но я ими очень редко пользовался. В js повсеместно использую.
Еще раз уточню: в php в основном сравнения на уровне скалярных величин происходят. В js сравнения используются гораздо шире.
Вообще всё-таки некорректно сравнивать эти языки. У них разная история и предназначение. Если задаться целью, то ровно также можно найти миллион отличий между javascript и java, js и go, php и pyton. Только вот зачем? Js — уникальный скриптовый язык, который на данный момент невозможно ничем заменить в отличие от серверных, где альтернатив много. Но от этого он не становиться идеальным. Как раз наоборот
Столько этих «особенностей» нет ни в одном другом языке.
История javascript .
Fi1osofДля меня эти особенности — в том числе и возможности. Так что я не против.
А я, например, часто пользуюсь функциями isInt, isArray и т.п. Не пойму, чем мы мериемся.
П.С. какая же это боль писать комменты с телефона.
Fi1osofЗатем подтянем PHP и познакомимся с declare(strict_types = 1);
После чего осознаем чем отличается функциональное программирование от ООП. Опробуем код на деле
Fi1osofstrict mode в js тоже есть, не знаю насколько их назначение идентично.
Fi1osofВсё это субъективно. Меня эти «возможности» немного раздражают, но это не значит, что js отстой. В обычных языках тоже свои особенности. Но они как есть, так и будут.
Тем не менее, developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions/Default_parameters
И это нужно только потому, что в JS можно упустить некоторые аргументы функций. А вот PHP выдаст ошибку. monosnap.com/file/SMXbyW9Hf6Nt6RglMF0drYg2fIxawk Так что изначально во всех аргументах были упущены особенности одного и другого языка и стали сравнивать не сравнимое.
Это как сравнивать английский с русским
Если сравнение русского и английского языка ты принял на свой счет в плане орфографии, то это говорит лишь о том, что ты читаешь по диагонали все, что тебе пытаются донести. Мой посыл был в том, что синтаксис абсолютно разный в русском и английском. Даже слова, похожие по написанию отличаются. Но это не говорит о том, что английски/русский плох — они просто разные. Точно так же и в контексте PHP/JS.
Возможно твои мысли где-то и верные, но аргументация вообще не из той области. Развивая тему с undefined, в том ключе который тебе нужен, то в php это решается так
Но мне в любом случае не удастся тебе донести мысль, что твои аргументы не продуманы и их нельзя даже использовать, поскольку лишь отображают или поверхностные знания, или не структурированный набор информации в головном мозге.
Для пущей убедительности была дана ссылка на документацию JS, где главная мысль это одна строка
Точно так же, как как и переменное количество параметров в PHP добавлено лишь с версии 5.6.
Это не говорит о том, что PHP плох или JS хорош. Это говорит лишь о том, что каждый язык развивается с оглядкой на другие языки. Поэтому наличие какой-то «фитчи» в PHP это еще не обязательно костыль. Точно так же, как и наличие «фитчи» в JS это не всегда «возможности». Языки просто разные и то, что делается в одном языке — не обязательно применимо к другому.
Возвращаясь к сравнению аглийского с русским, получается, что
— в англйском языке gallery с двумя буквами L это фитча. А в русском — возможность! Возможность писать слово короче, а значит быстрее.
— Или же все-таки галерея это фитча, т.к. слово словарное и его нужно специально запоминать. А в английском — возможность. Возможность писать слово так, как оно слишится
Ну и наконец
А как насчет этого? blog.sucuri.net/2013/09/ask-sucuri-non-alphanumeric-backdoors.html
Магия есть везде. Запомни это.
Fi1osof- Статусы ошибок — я так понимаю в подавляющем большинстве случаев ответ будет 200 и надо парсить тело ответа, чтобы определить что нам пришло. Не критично, но и не радует.
- Бесконечное количество вложенных запросов — насколько понял проблема решаема, можно создать «белый лист» запросов, ограничить вложенность, но это дополнительные телодвижения. Сюда же можно добавить вопрос, а что если клиент решит дернуть все сущности что есть на сайте с максимально допустимым лимитом в одном запросе? Как определить что прилетевший запрос слишком жирный?
- Контроль доступа к данным — ввиду того что неизвестно какой запрос придет от клиента, можно элементарно прошляпить и отдать ему то, что не положено. Конечно можно предоставить контраргумент, мол, можно и SQL инъекцию провтыкать, но на мой скромный взгляд вероятность накосячить с gql гораздо больше, особенно когда много сущностей и разного рода политик доступа.
- Кеширование — как я понял из того что пока читал, организовать кеширование довольно нетривиальная задача, опять таки из-за того что непонятно какой запрос будет выполнен.
- Целостность данных связанных сущностей при update если что-то пошло не так?
- Аналитика — в случае REST я могу повесить логирование каждого endpoint и спустя некоторое время, ознакомиться с полученными данными и в случае необходимости оптимизировать/переделать. В случае gql я себе смутно представляю как это сделать.
Единственное преимущество, которое я вижу в GraphQL это удобный доступ с клиента. Отсюда вытекает предположение, что данная технология подходит очень крупным компаниям, которые взаимодействуют с огромным числом разработчиков. Тогда думаю, будет оправдано заморочиться. Или если планируется раздавать большое количество контента без каких-либо хитрых операций с данными. Например, применение gql в каком нибудь сервисе геоданных я вижу вполне оправданным. В какой-то системе а-ля CRM с кучей хитрых операций, политик доступа, повышенными требованиями к безопасности — нет.Fi1osofВ php вообще не надо доступа к объекту ответа, там из любого положения можно задать заголовки, на сколько я помню.
Думаю, определять именно жирность запроса нет смысла. Я уже говорил, graphql ничего не знает о том, как будет выполняться непосредственно запрос. Он только парсит схему и определяет корректно ли был запрос написан или нет. Может у вас там запрос будет query {downloadInternetLatestVersion}. С точки зрения graphql это совсем не тяжелый запрос, одна операция всего, даже без параметров.
Но выставлять различные лимиты в graphql есть возможность.
medium.com/in-the-weeds/graphql-and-resource-limitations-442c3bd72358
Можно еще, как вариант, по времени выполнения ограничить (к примеру, запросы на чтение более 3 сек — это скорее всего уже слишком много).
Здесь несколько вариантов. Еще раз напомню: есть большая разница между схемой (которую обрабатывает graphql) и реальными запросами. По этой причине в большинстве случаев именно вы будете отвечать за безопасность. Но есть варианты.
1. Изучить схему через graphql-voyager habr.com/ru/company/mailru/blog/448354/
Еще до появления этой статьи я начал его активно использовать и он на сайте у меня с самого начала: prisma-cms.com/graphql-voyager
2. Непосредственно разграничение кому можно делать, а кому нельзя, это уже вам самим делать проверку. То есть есть схема Mutation.updateTopic. Вам надо будет персонализировать пользователя, проверить его ли это объект и т.п. У меня бОльшая часть базовой логики проходит через единый процессор, и там в том числе есть и проверка прав на объект.
Вот это точно. И по опыту я скажу: организовывать кеширование на стороне сервера — может выйти боком, это делайте в последнюю очередь. Лучше делайте кеширование на стороне браузера. В том же apollo-client есть мощные средства для кеширования. Можете зайти на prisma-cms.com, открыть dev-tools network и посмотреть ajax-запросы. Походите по одним и тем же страницам туда-сюда, и вы увидите, что далеко не все запросы постоянно повторяются.
Это у меня контролирует prisma.io. Там на все создаются первичные/вторичные ключи. Когда я отправляю на апдейт запрос с вложенностями, если что-то не так на любом уровне, выполняется откат на уровне БД (transaction rollback).
Я прям сейчас не могу ответить четко, потому что до этого плотно не дошел. Но средства уже есть, их просто надо изучить.
Вот в целом про логирование в node-js мне статья понравилась: habr.com/ru/company/funcorp/blog/461881/
Ну а непосредственно по graphql если погуглить «graphql performance monitoring», можно наверняка найти нужное. Вот, к примеру, у apollo-server есть: blog.apollographql.com/graphql-caching-and-performance-monitoring-in-minutes-with-apollo-engine-9b8e5da57bfb
Призма тоже юзает его, так что по идее средства мониторинга в ней уже есть, просто надо раскопать.
Вот тут вы, вероятно, заблуждаетесь. В который раз говорю: graphql — это только схемы (условно). Конечная логика — это уже на вас. Даже если у вас уже есть свое REST-API, совсем не лишним будет поставить GraphQL между ним и клиентом. Логика реального выполнения запросов у вас не поменяется. Но, во-первых, у вас всегда будет перед глазами API-схема и вы всегда будете понимать что вам может клиент в запросах отправить и что получить. Во-вторых, у вас будет только одна точка входа. Вы покопайте статьи по безопасности того же MODX: там закройте, здесь прикройте, здесь подмените и т.д. и т.п. И все равно на сайте (я про подобные танцы). И даже если вы все это сделаете, все равно у вас могут остаться дыры (Вы можете не знать, что где-то в assets появился php-файл коннектор, через который весь сайт можно ломануть, как это не раз было с gallery, login, phpthumb и т.п.). А потом еще зараза проникает и вообще весь сайт как решето. В случае использования graphql у вас только одна точка входа (если вы сами не добавили еще). Посмотрите внимательно prisma-cms.com, вы можете запросы отправлять только на prisma-cms.com/api/ или api.prisma-cms.com/ (которые ведут в итоге все равно в одну точку). Больше никуда. И даже когда я использовал связку graphql+MODX, логика вся выполнялась по-прежнему в MODX, но точка входа была только одна.
Fi1osofПосмотри в сторону railt github.com/railt/laravel-provider пилит его Кирилл Несмеянов — Активный участник Laravel сообщества.
Так же, советую к просмотру его доклад www.youtube.com/watch?v=wCfuvB-uPNE
Один только факт того, что проект качественно покрывается тестами — уже о многом говорит.
В свете сказанного, GraphQL выглядит немного неуместно для PHP. Как и для любого другого серверного языка. А вот для JS самое оно. Фронтэндеру не нужно разбираться в связях и типах. Всё прописано в схемах.
Но это, как требует говорить Николай, чисто моё мнение.
Я думаю это видео немного изменит твое мнение www.youtube.com/watch?v=ViB_lA54gqk
В общем получается такая картина, есть язык запросов — GraphQL. А есть серверная реализация этого языка запросов. Схема в общем случае на выходе одна и не важно чем и как ты ее генерируешь.
Если мы берем за аксионму тот факт, что GraphQL это чисто JS, а проект на PHP, то реализация API потребует абсолютно другого технологического стека. И бизнес логика сервисного слоя будет дублироваться на двух языках. Это приведет к удорожанию стоимости всего проекта. Одно дело, когда мы берем данные в сыром виде из базы. Другое дело, когда уже добавляется какая-то логика.
Ну или возьмем пример из топика. Что сделал ТС? Он подцепился к API modx.pro который разработал Василий и завернул ответ в GraphQL. И тут напрашивается резонный вопрос, а зачем нам этот промежуточный слой в виде уже существующего /assets/components/extras/action.php, если у нас есть доступ к коду и можно сразу сформировать GraphQL
Единственное, где может пригодится пример из топика — API к каким-то сотороним сервисам, к коду которых нет доступа, агрегирование нескольких API в один и т.п. Но все это задачи частного порядка.
А теперь откроем официальный сайт graphql.org/code/ и увидим серверную реализацию GraphQL на C#, Go, Java, PHP, Python, Ruby, etc… Для PHP указано аж 2 библиотеки. И одна из них webonyx/graphql-php взята за основу для Railt, который по своей сути является лишь синтаксическим сахаром для официального серверного пакета.
Так что кем бы ни был придуман GraphQL, но кидаться в омут npm пакетов и изучать шаманство в JS еще совсем не обязательно имея на руках проект PHP.
Я имел ввиду немного другое. Конечно не надо менять стеки. Я хотел сказать, что нет особой необходимости использовать GraphQL для PHP разработчиков. Если только у тебя не мега сервис с могущим API, как говорит Кирилл Несмеянов.Т.е.PHP разработчику вполне хватит REST-а.
А вот если посмотреть со стороны фронт разработчика, то ему GraphQL даёт больше плюшек. Он не знает ни схем, ни связей объектов в отличие от PHP разработчика (и любого другого бекэндера). И GraphQL даёт ему возможность самому строить запросы не зная этого.
Попробую пояснить мысль. Представим, что MODX — это сервис с API, как это изначально планировалось сделать в MODX3 — использовать Slim в качестве ядра и работать через API. Тогда можно подключать любую админку. Как мне кажется (!), тут GraphQL в качестве обёртки не нужен. Вполне хватит обычного REST-а.
П.С. Возможно, я говорю глупости. Просто ещё не до конца вкурил эту тему. :)
Когда PHP + JS разрабатывается одним человеком, то тут выбирай что хочешь. Но когда это разные люди или даже комманды. То GraphQL позволяет им работать независимо друг от друга.
В общем я не говорю, что REST это плохо. Я говорю о том, что когда твое API выходит за рамки 2-3 методов, то GraphQL способен упростить жизнь как при разработке, так и в поддержке.
Например, раньше я документировал API через raml.org
Мне казалось это верх удобства. Но сейчас в моем арсенале появился еще один инструмент.
З.Ы. Похоже тема топика уже более чем раскрыта. Всем спасибо за внимание:-)
Fi1osofP.S. А еще не раскрыта возможность использования forwardTo запросов, что позволяет как бы строить сеть микросервисов. Несколько отдельных API-серверов могут взаимодействовать друг с другом. Шлешь запрос на один сервер, а тот собирает информацию с разных серверов и возвращает все в одном ответе. Но это уже не совсем ядро. а различные реализации поверх.
и
Одно и то же разными словами.
А насчет углубления в документацию, соглашусь с комментатором из соседнего топика modx.pro/development/18727#comment-112808 тем более,
есть свое сообщество graphql.org/community/ и много мануалов в сети.
Я ничего не имею против топиков не связанных с modx на этом сайте. Но на мой взгляд, эти топки не должны выходить за рамки вольного пересказа возможностей других технологий. Иначе сайт превратиться из сообщества modx в помойку с набором несвязанных между собой технических статей. Один только топик про minecraft это верх профильности и информационной кладези. Можно я рядом создам топик про сервера для Counter Strike?
Fi1osofПосмотри внимательно код по ссылке.
Вот мы объявляем новый GraphQL-сервер:
Это наш сервер, и схема наша, то есть полноценный сервер. Мы можем создать несколько таких отдельных серверов, каждый со своим эндпоином.
И все это мы оборачиваем в единый сервер:
Что это дает?
1. При желании, мы можем эндпоинты подсерверов скрыть за фаервол или типа того, то есть не давать прямого доступа к подсерверу.
2. Не дублировать в резолверах функционал этих подсерверов. В резолвере, вместо того, чтобы переписывать код, мы просто переводим запрос на подсервер.
То есть это полезно, когда делал несколько отдельных проектов, и решил их все объединить в единое АПИ, но не переписывать код.
Если будет на js, я бы почитал.
github.com/VadimDez/Counter-Strike-JS
Fi1osofНа мой взгляд, агрегирование нескольких API в один, это задача частного уровня. А вот права доступа более чем насущная проблема, которая будет интересна большему кругу читателей.
Fi1osofЗа весь GraphQL не скажу, давно уже не работал напрямую с ним (ибо без дополнительных инструментов работать с ним тяжеловато местами). Но в призме есть возможность защитить эндпоинт, указан managementApiSecret в конфигурации docker-инстанса. www.prisma.io/docs/prisma-server/authentication-and-security-kke4/#prisma-server
В таком случае для работой по АПИ придется указывать токен доступа.
На счет данных внутри, тут хитрее задача (имеется ввиду, когда, к примеру, пользователю надо ограничить доступ к отдельным полям объектов в результатах запроса). Я пока универсального средства не нашел. Но пара моментов, которые я использую:
1. Резолверы на отдельные поля объектов.
К примеру, такая схема:
Мы можем прописать отдельные резолверы для каждого поля. К примеру:
Самое приятное в этом способе то, что это распространяется на любые выборки в любой части запроса/подзапроса. То есть даже если запросят Топики->Комментарии->КемСоздано(User)->{email, password}, даже в этом случае поля будут прогоняться через эти резолверы. И даже в мутациях и подписках.
Вот реальный код: github.com/prisma-cms/user-module/blob/7fc769cbf1635799442fdd798f6c2f209fa7a8c2/src/modules/index.mjs#L1203-L1295
2. Чистка лишних типов.
Пример выше годится тогда, когда права разграничиваются, а не просто исключаются (как в случае с емейлами, которые могут видеть сами пользователи, если это их емейл, и админы). Но бывают случаи, когда поля вообще надо исключить из схемы, чтобы их даже запрашивать не могли, чтобы даже не знали о их существовании. Вот тогда вообще приходится чистить схему.
К примеру, вот здесь я перечисляю те объекты, которые надо вычистить из схемы: github.com/prisma-cms/boilerplate/blob/05ace079c039c48fac5a936967a40297692001fa/src/server/modules/index.mjs#L99-L180
Сама чистка выполняется здесь.
А здесь я удаляю из схемы те методы, для которых резолверы не описаны. Это не совсем за секурность, но все же так и схема меньше получается, и видишь ты только то, для чего есть резолверы. А то намержишь кучу модулей, схема огромная, а реально резолверов десяток только.
Ну и самая дыра по безопасности, это мутации вложенные. К примеру для запросов
Вот такое, в отличие от резолверов на чтение, где объявил в одном месте и везде это прогоняется, просто так не заткнешь. Поэтому я на автомате вычищаю все create/update/upsert из связанных моделей. Но все равно еще есть узкие места. Вот эта задача до сих пор для меня не полностью разрешенная. Хотя на горизонте начинают маячить варианты.
P.S. В Go не силен пока, но эти приемы должны одинаково работать на любых имплементациях.
Fi1osofХорошо хоть получилось вытащить коммент из запроса в дубле вкладки, переписывать бы поленился.
— показать, что GraphQL это не серебрянная пуля. И на реальных проектах все может оказаться несколько сложнее.
— Очередной раз подчеркнуть, что разбирая тонкости какой-то технологий, мы все дальше углубляемся в лес.
Хотя, если бы мы обсуждали не абстрактную вещь в стиле «смотрите какая крутая штука есть. на ней можно делать то и се», а разбирали реализацию GraphQL хотя бы для modx_site_content + TV, то такой материал вызвал бы
— намного больше интереса в сообществе
— позволил бы углубиться в тонкости технологии с пользой для CMS и т.п.
Про Go запрос был шутки ради. Но на всякий случай оставлю это тут github.com/gopherjs/gopherjs
Fi1osofТам много материала для изучения и форвардинга запросов на MODX вместе с кукисами (для авторизации), и работа с БД напрямую (не только modx_site_content). Если кому интересно, здесь частично схема: github.com/prisma-cms/boilerplate-modx/tree/master/src/server/modules/modx/schema/api
В общем, наработки были, но развивать их не стал за отсутствием надобности.
В любом случае, если бы я раньше увидел этот топик, то давно бы тебе показал это youtu.be/xETUOC4M4m4
И если бы обошлось без визгов, то мы бы конструктивно поговорили на тему ограничения прав доступа в GraphQL запросах. Затронули бы тему раскрытия путей /var/www/modxclub.ru/modxclub-3.0/ и еще много чего.
Fi1osofВо-вторых, про перебор паролей (и емейлов и телефонов), я знаю. Но уровень безопасности должен соответствовать ценности информации. Даже если ты там все сломаешь, пофигу. Сейчас задачи другие — нарабатывать функционал. Скажем так, я дико сомневаюсь, что ты можешь разработать такой фронт-редактор. Получается, все буду делать я, а безопасность будем делать мы? Нет. До безопасности доберусь, когда действительно понадобится (самые важные я заткнул дыры, а шифрованный пароль даже если ты получишь, тебе его еще надо будет дешифровать, тоже не простая задача).
И если вдруг движок станет популярным (хоть и не факт), он open-source, найдутся и другие умельцы по безопасности, кто сделает свой вклад.
P.S. Кстати, если получится все-таки подобрать пароль, покажи. А то пока что 9 символов подобрал, а там их 60…
Столько лет прошло, а ты так и не осознал мысль, что если кто-то способен найти твои ошибки, то значит этот кто-то знает больше твоего. Да, это не всегда так. Иногда это говорит о невнимательности разработчика, но этот случай явно не про тебя. Уж очень часто ты диалог заканчиваешь фразой «до безопасности доберусь, когда действительно понадобится».
Fi1osofВо-вторых, ты ведешь себя как всякая учка в школе, которая считает, что ее предмет важнее других (чел увлекается химией, но учка по ИЗО ругается на него, что он плохо рисует). Еще раз повторяю: клал я на твою безопасность, ровно как на SEO и т.п. Тебе нравится заниматься ей? Занимайся. Но не думай, что все обязаны ей заниматься. И то, что ты в ней шаришь, это еще не много значит (примерно как всякую птицу не особо парит, что какая-то обезьяна лучше по деревьям прыгает).
Как-то так...
Что ты можешь? Ты даже еще не осознал что там вообще происходит, а уже может… Посмотри на свой профиль в гитхабе и на мой. Хотя бы просто по активности. Я вижу, что ты можешь копировать чужие репозитории. Ровно с тем же успехом ты можешь скопировать мои и сказать «Я смог». А наделе ты даже близко не занимаешься такими вещами, у тебя другой профиль.
Но по теме ветки комментариев. Мы начали разговор за реальные кейсы на GraphQL. Я понимаю, что тебе тема безопасности не важна, на SEO срать и т.д. Но если твой девиз хуяк-хуяк и в прод, то есть разработчики/студии которые стараются выпускать все-таки качественный со всех сторон продукт. И именно им в первую очередь будет познавательно узнать о реальных кейсах, а не каких-то абстрактных типо агрегирования нескольких API в один.
Но даже разбирая реальные кейсы, вместо накидывая путей решений ты тыкаешь носом в свои обрывки кода, отправляешь смотреть проекты и наработки. Но при этом на конструктивную критику ни в каком направлении не принимаешь. Если бы мы обсуждали абстрактные пути решения, статьи с того же хабра, сторонние репозитории и т.п., то диалог мог быть бы абсолютно другим. Скорее всего, мы бы поговорили за подходы к реализации как там сделано и как можно по другому. Какие плюсы и минусы выбранных реализаций и т.д. Но нет, ты тешишь свое ЧСВ показывая именно свои нарабокти, но не готов их обсуждать в негативном ключе. При этом пытаешься ущипнуть меня тем, что я якобы ничего не способен реализовать. Но не задумывался ли ты о том, что
Fi1osofХотя сам Кирилл её не хвалит )
Но в будущем, я все-таки думаю у Railt-а хорошие перспективы, т.к. код без жесткой завязки на фреймворк. А это значит, что его можно брать за основу не только в Laravel.
Да, Railt на перспективу интересный, но я вот пошарился по доке — очень много пустых разделов, мануалов хотя бы минимальных вообще нет, а я не настолько про чтобы полностью ориентироваться по исходникам. Какие-то моменты можно раскурить по коду, но не настолько. По времени дешевле взять что-то более готовое. Собственно поэтому и начал искать альтернативное решение. Этот github.com/folkloreinc/laravel-graphql как я понял умер, этот github.com/rebing/graphql-laravel что-то не очень понравился (субъективно).
Возьму пока Lighthouse вроде все для старта есть, уже даже апишка получается минимальная буквально со старта.