Создаем свое modx.pro API на GraphQL

Всем привет!

Поддержу возникший марафон и напишу свою заметку в раздел «Для разработчиков». Думаю, многим понравится, тем более что тестовые примеры будут касаться в том числе и самого сайта 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{
foo_string
foo_number
foo_json
}
Вроде как Query могут как попало по очередности выполниться, а Mutation строго последовательно, как перечислено в запросе. Но это не точно и не особо суть, так, на заметку.

Ну как? Попробовали выполнить запросы? Если да и готовы продолжать дальше, то давайте разберем уже более внимательно что и как тут работает. Перейдем непосредственно к установке.

Для начала по хостингу: методом проб и ошибок я установил, что modhost.pro/ в общих чертах подходит для экспериментов, хотя на тестовом тарифном плане не получится (хотя в официальной тарифной сетке указано 250 метров диска будет доступно, на практике чуть больше ста, и запись на диск прерывалась (80 метров в кеш npm и финита ля комедия)). Но на минимальном тарифном плане за 75 рэ в месяц все полетело нормально. Хотя предполагаю, что эта лавочка может прикрыться, потому что нода сама по себе требует много ресурсов (та же установка всех зависимостей требует порядка гигабайта оперативки, и это в среднем в течение минуты). На сервере, который мне выделился, сейчас 10 Гб оперативы свободно. Если 10 человек одновременно запустит установку зависимостей, сервер заплачет. То есть хотя на php-процессы и есть лимиты, на нод-процессы я лимитов не увидел. Так что у кого есть возможность тестировать на своем железе, просьба: лучше там (хоть локально). Но если альтернативы нет, то наверно можно и там.

1. Заходим по ssh на сервер. создаем себе где-нибудь папку для проекта и клонируем туда мой репозиторий.
git clone https://github.com/Fi1osof/api.modx.pro
2. Переходим в папку скаченного репозитория и выполняем установку зависимостей.
npm i
3. Выполняем сборку API-схемы.
npm run build-api
4. Запускаем 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
}
И в резолвере дописал значения по умолчанию. ОК, полетело. Теперь получаем ответ:
{
«data»: {
«modx_pro_users_connection»: {
«success»: false,
«message»: «Access denied»,
«data»: [],
«limit»: 0,
«start»: null,
«total»: 0,
«pages»: [],
«results»: []
}
}
}
Ошибся с форматом поля num.
В схеме для данных постраничности я прописал:
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
Fi1osof
08 августа 2019, 07:58
modx.pro
1
3 212
+11

Комментарии: 82

Алексей Соин
08 августа 2019, 08:23
+1
Ого! Сложно, но интересно. Давно хотел узнать, как в MODX сделать API, даже как то на modx-клубе спрашивал у тебя по данной теме, а тут уже два варианта за несколько дней))) Спасибо!
    Fi1osof
    08 августа 2019, 08:34
    0
    Ну вот как освоишь оба способа, так сравнишь какой лучше :)
      Павел Гвоздь
      08 августа 2019, 11:46
      0
      какой лучше
      Это смотря для каких целей.
      — Если тупо всю необходимую инфу получать и выводить о товарах, то RESTful подойдёт, он же проще ставится и работает только на PHP, на котором и сам MODX крутится.
      — Если же нужна универсальность API, для какого-то мобильного приложения или SPA, то GraphQL будет предпочтительнее. Правда работы изначально придётся провести довольно много, чтобы прописать всё взаимодействие (запросы, мутации, подписки) в резолверы.
        Fi1osof
        08 августа 2019, 11:53
        +1
        то RESTful подойдёт, он же проще ставится и работает только на PHP, на котором и сам MODX крутится.
        GraphQL реализация есть и на php.

        Правда работы изначально придётся провести довольно много, чтобы прописать всё взаимодействие (запросы, мутации, подписки) в резолверы.
        Тут вопрос спорный. Уже довольно много средств есть для генерации всего и вся, в том числе резолверов. К примеру, на той же призме, достаточно описать схему и запустить деплой, будет не только структура БД создана, но и сгенерированы CRUD-резолверы, включая условия поиска, сортировки и т.п.
          Павел Гвоздь
          08 августа 2019, 12:21
          0
          GraphQL реализация есть и на php.
          В курсе, а ещё на куче других языков. Но в данном же случае обсуждается твой вариант реализации (на JS) и вариант Андрея (на PHP).

          Тут вопрос спорный. Уже довольно много средств есть для генерации всего и вся, в том числе резолверов. К примеру, на той же призме, достаточно описать схему и запустить деплой, будет не только структура БД создана, но и сгенерированы CRUD-резолверы, включая условия поиска, сортировки и т.п.
          То есть, на примере ИМ, можно просто описать схему запросов и мутаций для msCategory, msProduct, msOrder и т.п. и оно поедет?
            Fi1osof
            08 августа 2019, 12:55
            0
            В курсе, а ещё на куче других языков. Но в данном же случае обсуждается твой вариант реализации (на JS) и вариант Андрея (на PHP).
            Но если бы я все еще писал на php, то практически все из описанного было бы так же, просто на php.

            То есть, на примере ИМ, можно просто описать схему запросов и мутаций для msCategory, msProduct, msOrder и т.п. и оно поедет?
            В данном случае нет, потому что ИМ не на призме. Но если поискать, то можно наверняка что-нибудь найти для этого (но при условии наличия прямого доступа к БД, а не как я тут просто тяну POST-запросами). Вот вроде как пример: github.com/overblog/GraphQLPhpGenerator
    Павел Гвоздь
    08 августа 2019, 12:16
    +3
    рассчитывая, что ответ придет в соответствии с документацией
    тогда-то и посыпались ошибки
    примерно это я и здесь буду сейчас это проходить
    Смею возразить, что не «это» и даже не «примерно». Ведь когда тебе разработчик с того конца даёт доку, то ты рассчитываешь, что в ней отражены все нюансы и тонкости, если нет — это плохой тон. В данном случае автор т.н. API, которое мы фактически парсим, ничего нам не должен и в любой момент может поменять любой тип данных от чего GraphQL начнёт ругаться.

    К слову, я вообще по себе заметил, что работая с JS я стал более внимательным к типам данных и условиям
    Вот это неоспоримый факт! Я порой офигеваю от того, что я делал раньше на PHP. Жжжуть…

    7. modstore.pro и modx.pro судя по всему — это абсолютно разные сайты со своими отдельными базами данных.
    Так и есть, Василий ни раз об этом говорил.
      Fi1osof
      08 августа 2019, 12:51
      +1
      В данном случае автор т.н. API, которое мы фактически парсим, ничего нам не должен и в любой момент может поменять любой тип данных от чего GraphQL начнёт ругаться.
      Так-то оно так. Но если бы на той стороне использовалось graphql-API, то я мог бы посмотреть сгенерированную API-документацию и легко сориентироваться что там не так, и что можно запрашивать, а что нет. Ведь graphql-документация всегда актуальная.

        Сергей Шлоков
        08 августа 2019, 18:30
        0
        Вот это неоспоримый факт! Я порой офигеваю от того, что я делал раньше на PHP. Жжжуть…
        А можно уточнить в чём неоспоримость? Оба языка вроде как с динамической типизацией? А с PHP 7 появилась статическая типизация.
          Павел Гвоздь
          08 августа 2019, 18:48
          0
          TS, как минимум, теперь приходится чаще использовать. Контекст не в том, что JS «типизированнее» PHP…
            Сергей Шлоков
            08 августа 2019, 19:02
            0
            Просто для меня это заявление звучит противоречиво.
            Говориться, что на JS приходится более внимательно работать с типами и условиями. Т.е. он менее требователен и типизирован. Что в дальнейшем может приводить к ошибкам. И следом идет
            Я порой офигеваю от того, что я делал раньше на PHP. Жжжуть…
            Т.е. всё наоборот.

            Поэтому и просил уточнить.

            П.С. А TS — это typescript?
          Fi1osof
          09 августа 2019, 01:05
          +6
          А можно уточнить в чём неоспоримость? Оба языка вроде как с динамической типизацией?
          Да, так и есть. Но в JS это гораздо чаще выходит боком.

          К примеру, в 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 будешь невнимательным к типам, просто не сможешь программировать нормально.
            Сергей Шлоков
            09 августа 2019, 09:25
            0
            Так и я про то же. Но почему-то жуть на php. Есть сайты с задачками для js именно про эти особенности.
              Fi1osof
              09 августа 2019, 10:17
              0
              Вероятней всего, если ты вдруг начнешь плотно на js программировать, ты согласишься с нами. А пока нет, сложно будет для тебя примеры привести.
                Сергей Шлоков
                09 августа 2019, 10:44
                +1
                Возможно. Никогда не говори никогда. Но сейчас я не понимаю, почему описанные выше и ещё в тысячах статей выкрутасы в js это просто прикольные мелочи, а в php всё просто ужасно. Поэтому хочется пояснений, раскрывающих преимущества js. Ответ — когда-нибудь поймёшь, говорит только о субъективности. Никаких объективных причин нет.
                  Fi1osof
                  09 августа 2019, 12:05
                  0
                  В целом да, все субъективно. Но обрати внимание, что я не говорил про ужасы. Я говорил просто про то, что стал более внимательно относиться к типам. И расшифрую это так: я просто получил больше гибкости в условиях и т.п. Обрати внимание, даже в официальной документации в сравнениях используют два знака равно. https://www.php.net/manual/ru/function.empty.php
                  Используйте вместо него trim($name) == false
                  !isset($var) || $var == false
                  Это говорит о том, что в принципе изначально особо и не заморачивались на счет жесткости сравнения. И я когда на 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?

                  При этом смотри какой интересный момент:
                  $v;
                  print (int) ($v === null);
                  print (int) (empty($v));
                  Получаем «PHP notice: Undefined variable: v», но «11», то есть необъявленная переменная таки === null.
                  В js undefuned !== null, то есть для меня это дополнительные условия сравнения.

                  3. instanceof.
                  4. typeof.

                  3 и 4 аналоги тоже есть в php, но я ими очень редко пользовался. В js повсеместно использую.

                  Еще раз уточню: в php в основном сравнения на уровне скалярных величин происходят. В js сравнения используются гораздо шире.
                    Сергей Шлоков
                    09 августа 2019, 15:55
                    0
                    Еще один из примеров особенностей js.
                    Например, тип строки (Hello world) не совпадает с типом нового объекта String (new String('Hello world')). Это порой приводит к нежелательным последствиям, которые могут сбить с толку.
                    Вообще всё-таки некорректно сравнивать эти языки. У них разная история и предназначение. Если задаться целью, то ровно также можно найти миллион отличий между javascript и java, js и go, php и pyton. Только вот зачем? Js — уникальный скриптовый язык, который на данный момент невозможно ничем заменить в отличие от серверных, где альтернатив много. Но от этого он не становиться идеальным. Как раз наоборот
                    Столько этих «особенностей» нет ни в одном другом языке.
                        Fi1osof
                        10 августа 2019, 00:30
                        0
                        тип строки (Hello world) не совпадает с типом нового объекта String (new String('Hello world'))
                        Совсем не удивительно, ведь тип строки (строка) не может совпадать с типом нового объекта (объект). «string» !== «object».

                        Столько этих «особенностей» нет ни в одном другом языке.
                        Для меня эти особенности — в том числе и возможности. Так что я не против.
                        Сергей Шлоков
                        09 августа 2019, 16:04
                        0
                        3. instanceof.
                        4. typeof.

                        3 и 4 аналоги тоже есть в php, но я ими очень редко пользовался. В js повсеместно использую
                        Честно говоря непонятно, что ты этим хотел подчеркнуть. Ибо понять это можно как минус.
                        А я, например, часто пользуюсь функциями isInt, isArray и т.п. Не пойму, чем мы мериемся.

                        П.С. какая же это боль писать комменты с телефона.
                          Сергей Шлоков
                          09 августа 2019, 16:22
                          0
                          <Часто ли ты используешь сравнение с null?
                          Не часто, но бывает. Я мыслю по другому. я так программирую, что мне достаточно isset и isEmpty. И я не припомню случая, когда мне нужно было бы проверять на undefined. А вот в js это частенько приходилось делать. Это потому что я плохой программист или просто подход к языкам разный?
                          Евгений Борисов
                          11 августа 2019, 12:55
                          +3
                          Бред сивой кобылы. Для начала откроем таблицу dorey.github.io/JavaScript-Equality-Table/ и убедимся, что сравнение == это беда не только PHP, но и JS.

                          Затем подтянем PHP и познакомимся с declare(strict_types = 1);
                          <?php declare(strict_types = 1);
                          
                          function test(int $id) {
                          	return $id;
                          }
                          
                          var_dump(test(2));
                          var_dump(test("2"));
                          После чего осознаем чем отличается функциональное программирование от ООП. Опробуем код на деле
                          $obj = new class {
                          	protected $a = null;
                          };
                          var_dump(property_exists($obj, 'a')); // true
                          var_dump(property_exists($obj, 'b')); // false
                            Fi1osof
                            11 августа 2019, 13:25
                            0
                            и убедимся, что сравнение == это беда не только PHP, но и JS.
                            Я где-то говорил обратное, или просто доебаться хотелось?

                            Затем подтянем PHP и познакомимся с declare(strict_types = 1);
                            strict mode в js тоже есть, не знаю насколько их назначение идентично.
                              Евгений Борисов
                              11 августа 2019, 13:42
                              0
                              Перечитай внимательно свои комментарии. Мысли прыгают с 5 на 10. Начали за == в php. Потом резко на === в JS. И дальше все в таком же ключе твоего собственного опыта. О чем это говорит? Это говорит о том, что в PHP знания ниже среднего. Так чего ты тут усердно доказываешь своими комментариями?
                                Fi1osof
                                11 августа 2019, 13:53
                                0
                                ОК. Пусть у меня будут знания ниже среднего вообще во всем. Диалог закрыт.
                            Сергей Шлоков
                            12 августа 2019, 10:06
                            +1
                            сравнение == это беда не только PHP, но и JS.
                            Ты не понял. В js это «возможности», а в php — беда. )

                            Всё это субъективно. Меня эти «возможности» немного раздражают, но это не значит, что js отстой. В обычных языках тоже свои особенности. Но они как есть, так и будут.
                              Евгений Борисов
                              12 августа 2019, 10:58
                              0
                              Ты не понял. В js это «возможности», а в php — беда. )
                              Я примерно так и понял. Просто многие аргументы в стиле там мне не нравится, а тут нравится. И тем не менее, undefined зачастую используется в ключе
                              foo === undefined чтобы проверить была ли вообще передана переменная (в php аналог isset).
                              Тем не менее, developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions/Default_parameters

                              И это нужно только потому, что в JS можно упустить некоторые аргументы функций. А вот PHP выдаст ошибку. monosnap.com/file/SMXbyW9Hf6Nt6RglMF0drYg2fIxawk Так что изначально во всех аргументах были упущены особенности одного и другого языка и стали сравнивать не сравнимое.

                              Это как сравнивать английский с русским
                              • галерея => gallery
                              • адрес => address
                              • паспорт => passport
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Евгений Борисов
                                12 августа 2019, 12:15
                                1
                                0
                                Не путай божий дар с яичницей. Обсуждение в данной ветке комментариев вообще не относится к твоему топику. Оно относится лишь к сравнению PHP и JS.

                                Если сравнение русского и английского языка ты принял на свой счет в плане орфографии, то это говорит лишь о том, что ты читаешь по диагонали все, что тебе пытаются донести. Мой посыл был в том, что синтаксис абсолютно разный в русском и английском. Даже слова, похожие по написанию отличаются. Но это не говорит о том, что английски/русский плох — они просто разные. Точно так же и в контексте PHP/JS.

                                Возможно твои мысли где-то и верные, но аргументация вообще не из той области. Развивая тему с undefined, в том ключе который тебе нужен, то в php это решается так
                                <?php function a(...$b){var_dump($b);}
                                Но мне в любом случае не удастся тебе донести мысль, что твои аргументы не продуманы и их нельзя даже использовать, поскольку лишь отображают или поверхностные знания, или не структурированный набор информации в головном мозге.

                                Для пущей убедительности была дана ссылка на документацию JS, где главная мысль это одна строка
                                Ранее для проверки и задания стандартных значений использовалось тело функции, где параметры сравнивались с undefined
                                Точно так же, как как и переменное количество параметров в PHP добавлено лишь с версии 5.6.
                                Это не говорит о том, что PHP плох или JS хорош. Это говорит лишь о том, что каждый язык развивается с оглядкой на другие языки. Поэтому наличие какой-то «фитчи» в PHP это еще не обязательно костыль. Точно так же, как и наличие «фитчи» в JS это не всегда «возможности». Языки просто разные и то, что делается в одном языке — не обязательно применимо к другому.

                                Возвращаясь к сравнению аглийского с русским, получается, что
                                — в англйском языке gallery с двумя буквами L это фитча. А в русском — возможность! Возможность писать слово короче, а значит быстрее.
                                — Или же все-таки галерея это фитча, т.к. слово словарное и его нужно специально запоминать. А в английском — возможность. Возможность писать слово так, как оно слишится

                                Ну и наконец
                                Или вот: (![] +[])[+!![]] = 'а'
                                А как насчет этого? blog.sucuri.net/2013/09/ask-sucuri-non-alphanumeric-backdoors.html
                                Магия есть везде. Запомни это.
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Это сообщение было удалено
                                Сергей Шлоков
                                12 августа 2019, 14:34
                                0
                                А как насчет этого?
                                Ого! Шаманы, мать их.
            Сергей Шлоков
            08 августа 2019, 19:06
            +1
            Спасибо за статью. Глубоко ещё не вчитывался. Но так как она в векторе моего интереса, то мне уже интересно.
            Alex
            13 августа 2019, 06:00
            +1
            Николай, спасибо за статью. С точки зрения фронтенда довольно удобная штука. Буквально недавно решил поинтересоваться GraphQL (php) но… как-то оно неоднозначно выглядит и вызывает больше вопросов чем ответов, скорее всего из-за отсутствия опыта с данной технологией, а может и нет.
            1. Статусы ошибок — я так понимаю в подавляющем большинстве случаев ответ будет 200 и надо парсить тело ответа, чтобы определить что нам пришло. Не критично, но и не радует.
            2. Бесконечное количество вложенных запросов — насколько понял проблема решаема, можно создать «белый лист» запросов, ограничить вложенность, но это дополнительные телодвижения. Сюда же можно добавить вопрос, а что если клиент решит дернуть все сущности что есть на сайте с максимально допустимым лимитом в одном запросе? Как определить что прилетевший запрос слишком жирный?
            3. Контроль доступа к данным — ввиду того что неизвестно какой запрос придет от клиента, можно элементарно прошляпить и отдать ему то, что не положено. Конечно можно предоставить контраргумент, мол, можно и SQL инъекцию провтыкать, но на мой скромный взгляд вероятность накосячить с gql гораздо больше, особенно когда много сущностей и разного рода политик доступа.
            4. Кеширование — как я понял из того что пока читал, организовать кеширование довольно нетривиальная задача, опять таки из-за того что непонятно какой запрос будет выполнен.
            5. Целостность данных связанных сущностей при update если что-то пошло не так?
            6. Аналитика — в случае REST я могу повесить логирование каждого endpoint и спустя некоторое время, ознакомиться с полученными данными и в случае необходимости оптимизировать/переделать. В случае gql я себе смутно представляю как это сделать.
            Единственное преимущество, которое я вижу в GraphQL это удобный доступ с клиента. Отсюда вытекает предположение, что данная технология подходит очень крупным компаниям, которые взаимодействуют с огромным числом разработчиков. Тогда думаю, будет оправдано заморочиться. Или если планируется раздавать большое количество контента без каких-либо хитрых операций с данными. Например, применение gql в каком нибудь сервисе геоданных я вижу вполне оправданным. В какой-то системе а-ля CRM с кучей хитрых операций, политик доступа, повышенными требованиями к безопасности — нет.
              Fi1osof
              13 августа 2019, 06:56
              +2
              Статусы ошибок — я так понимаю в подавляющем большинстве случаев ответ будет 200 и надо парсить тело ответа, чтобы определить что нам пришло. Не критично, но и не радует.
              На сколько я знаю, в контекст выполнения запроса всегда передаются объекты запроса и ответа (во всяком случае в JS и во всяком случае в prisma.io). По этой причине нет проблемы установить свой код ответа.
              me: (source, args, ctx, info) => {
              ctx.response.statusCode = 500;
              return {
              id: «DSfdsf»
              }
              }
              В 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-запросы. Походите по одним и тем же страницам туда-сюда, и вы увидите, что далеко не все запросы постоянно повторяются.

              Целостность данных связанных сущностей при update если что-то пошло не так?
              Это у меня контролирует prisma.io. Там на все создаются первичные/вторичные ключи. Когда я отправляю на апдейт запрос с вложенностями, если что-то не так на любом уровне, выполняется откат на уровне БД (transaction rollback).

              Аналитика — в случае REST я могу повесить логирование каждого endpoint и спустя некоторое время, ознакомиться с полученными данными и в случае необходимости оптимизировать/переделать. В случае gql я себе смутно представляю как это сделать.
              Я прям сейчас не могу ответить четко, потому что до этого плотно не дошел. Но средства уже есть, их просто надо изучить.
              Вот в целом про логирование в 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
              Призма тоже юзает его, так что по идее средства мониторинга в ней уже есть, просто надо раскопать.

              В какой-то системе а-ля CRM с кучей хитрых операций, политик доступа, повышенными требованиями к безопасности — нет.
              Вот тут вы, вероятно, заблуждаетесь. В который раз говорю: 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, но точка входа была только одна.
                Alex
                13 августа 2019, 07:33
                0
                Спасибо за развернутый ответ. Все таки попробую GraphQL, только на ларке :). Очень подкупает удобство написания запросов с фронта, типизация входных данных и возможность не создавать 100500 роутов. Почти все вопросы как я вижу решаемы.
                  Fi1osof
                  13 августа 2019, 07:36
                  0
                  Пожалуйста.
                    Евгений Борисов
                    13 августа 2019, 10:37
                    1
                    +3
                    Все таки попробую GraphQL, только на ларке

                    Посмотри в сторону railt github.com/railt/laravel-provider пилит его Кирилл Несмеянов — Активный участник Laravel сообщества.

                    Так же, советую к просмотру его доклад www.youtube.com/watch?v=wCfuvB-uPNE
                      Alex
                      13 августа 2019, 16:04
                      0
                      Спасибо. Обязательно ознакомлюсь.
                        Alex
                        13 августа 2019, 19:22
                        0
                        Ознакомился. Единственное что смущает, что с момента митапа прошло уже больше года и до сих пор нет релиза и проект находится в dev версии. Непонятно можно ли это со спокойной душой юзать на продакшене. Может релиз и вовсе не состоится, всякое бывает.
                          Евгений Борисов
                          13 августа 2019, 20:00
                          +1
                          github.com/railt/railt/releases ну, а что касается продакшина, все относительно. В любом случае, на новых технологиях я бы не рискнул делать сложный проект. А так, ради поиграться если нужно что-то не очень сложное — очень даже ничего себе.

                          Один только факт того, что проект качественно покрывается тестами — уже о многом говорит.
                            Сергей Шлоков
                            13 августа 2019, 20:19
                            0
                            Посмотрел. Заинтересовало. Но… GraphQL — это как бы альтернатива REST-у. Придумана фронтэндерами. Удобно. Но в экосистеме MODX я не встречал даже какой-то толковой наработки по RESTful. Поэтому GraphQL вряд ли будет востребован разработчиками, использующими MODX. И это не удивительно. Могу ошибаться, но скорее всего и в WP, Joomla и т.п. CMS такого тоже не увидишь. Это другой уровень.
                            В свете сказанного, GraphQL выглядит немного неуместно для PHP. Как и для любого другого серверного языка. А вот для JS самое оно. Фронтэндеру не нужно разбираться в связях и типах. Всё прописано в схемах.
                            Но это, как требует говорить Николай, чисто моё мнение.
                              Евгений Борисов
                              13 августа 2019, 20:46
                              0
                              GraphQL выглядит немного неуместно для PHP. Как и для любого другого серверного языка. А вот для JS самое оно. Фронтэндеру не нужно разбираться в связях и типах. Всё прописано в схемах.

                              Я думаю это видео немного изменит твое мнение www.youtube.com/watch?v=ViB_lA54gqk
                              Евгений Борисов
                              13 августа 2019, 23:31
                              +2
                              Зацепил меня данный комментарий. Думал после видео что-нибудь кто-нибудь ответит, но нет)

                              В общем получается такая картина, есть язык запросов — 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.
                                Сергей Шлоков
                                14 августа 2019, 08:16
                                0
                                Зацепил меня данный комментарий. Думал после видео что-нибудь кто-нибудь ответит, но нет)
                                Я ещё первое до конца не досмотрел. А второе вообще 3 часа. ))

                                Если мы берем за аксионму тот факт, что GraphQL это чисто JS, а проект на PHP, то реализация API потребует абсолютно другого технологического стека.
                                Я имел ввиду немного другое. Конечно не надо менять стеки. Я хотел сказать, что нет особой необходимости использовать GraphQL для PHP разработчиков. Если только у тебя не мега сервис с могущим API, как говорит Кирилл Несмеянов.Т.е.PHP разработчику вполне хватит REST-а.

                                А вот если посмотреть со стороны фронт разработчика, то ему GraphQL даёт больше плюшек. Он не знает ни схем, ни связей объектов в отличие от PHP разработчика (и любого другого бекэндера). И GraphQL даёт ему возможность самому строить запросы не зная этого.

                                Попробую пояснить мысль. Представим, что MODX — это сервис с API, как это изначально планировалось сделать в MODX3 — использовать Slim в качестве ядра и работать через API. Тогда можно подключать любую админку. Как мне кажется (!), тут GraphQL в качестве обёртки не нужен. Вполне хватит обычного REST-а.

                                П.С. Возможно, я говорю глупости. Просто ещё не до конца вкурил эту тему. :)
                                  Евгений Борисов
                                  14 августа 2019, 09:33
                                  0
                                  До кучи вот еще один топик habr.com/ru/company/piter/blog/424037/

                                  Когда PHP + JS разрабатывается одним человеком, то тут выбирай что хочешь. Но когда это разные люди или даже комманды. То GraphQL позволяет им работать независимо друг от друга.

                                  В общем я не говорю, что REST это плохо. Я говорю о том, что когда твое API выходит за рамки 2-3 методов, то GraphQL способен упростить жизнь как при разработке, так и в поддержке.

                                  Например, раньше я документировал API через raml.org
                                  Мне казалось это верх удобства. Но сейчас в моем арсенале появился еще один инструмент.

                                  З.Ы. Похоже тема топика уже более чем раскрыта. Всем спасибо за внимание:-)
                                    Fi1osof
                                    14 августа 2019, 09:43
                                    +1
                                    Тема еще не до конца раскрыта. Еще не рассказывалось толком ничего про подписки (Subscription), но это уже отдельная тема и отдельный топик.

                                    P.S. А еще не раскрыта возможность использования forwardTo запросов, что позволяет как бы строить сеть микросервисов. Несколько отдельных API-серверов могут взаимодействовать друг с другом. Шлешь запрос на один сервер, а тот собирает информацию с разных серверов и возвращает все в одном ответе. Но это уже не совсем ядро. а различные реализации поверх.
                                    Евгений Борисов
                                    14 августа 2019, 10:47
                                    0
                                    API к каким-то сотороним сервисам, к коду которых нет доступа, агрегирование нескольких API в один и т.п.

                                    и
                                    Шлешь запрос на один сервер, а тот собирает информацию с разных серверов и возвращает все в одном ответе.
                                    Одно и то же разными словами.

                                    Тема еще не до конца раскрыта. Еще не рассказывалось толком ничего про ....
                                    А насчет углубления в документацию, соглашусь с комментатором из соседнего топика modx.pro/development/18727#comment-112808 тем более,
                                    есть свое сообщество graphql.org/community/ и много мануалов в сети.

                                    Я ничего не имею против топиков не связанных с modx на этом сайте. Но на мой взгляд, эти топки не должны выходить за рамки вольного пересказа возможностей других технологий. Иначе сайт превратиться из сообщества modx в помойку с набором несвязанных между собой технических статей. Один только топик про minecraft это верх профильности и информационной кладези. Можно я рядом создам топик про сервера для Counter Strike?
                                    Fi1osof
                                    14 августа 2019, 11:06
                                    0
                                    Одно и то же разными словами.
                                    Нет, в данном случае не одно и то же. Уточню.

                                    Посмотри внимательно код по ссылке.
                                    const {forwardTo} = require('prisma-binding')
                                    
                                    const resolvers = {
                                      Query: {
                                        posts: forwardTo('db')
                                      }
                                    }
                                    
                                    const server = new GraphQLServer({
                                      typeDefs: './src/schema.graphql',
                                      resolvers,
                                      context: req => ({
                                        ...req,
                                        db: new Prisma({
                                          typeDefs: 'src/generated/prisma.graphql',
                                          endpoint: '...',
                                          secret: 'mysecret123',
                                        }),
                                        debug: true,
                                      }),
                                    })
                                    
                                    server.start(
                                      () => console.log(`Server is running on http://localhost:4000`),
                                    )
                                    Вот мы объявляем новый GraphQL-сервер:
                                    db: new Prisma({
                                          typeDefs: 'src/generated/prisma.graphql',
                                          endpoint: '...',
                                          secret: 'mysecret123',
                                        }),
                                    Это наш сервер, и схема наша, то есть полноценный сервер. Мы можем создать несколько таких отдельных серверов, каждый со своим эндпоином.

                                    И все это мы оборачиваем в единый сервер:
                                    const server = new GraphQLServer({
                                      typeDefs: './src/schema.graphql',
                                      resolvers,
                                    Что это дает?
                                    1. При желании, мы можем эндпоинты подсерверов скрыть за фаервол или типа того, то есть не давать прямого доступа к подсерверу.
                                    2. Не дублировать в резолверах функционал этих подсерверов. В резолвере, вместо того, чтобы переписывать код, мы просто переводим запрос на подсервер.
                                    const resolvers = {
                                      Query: {
                                        posts: forwardTo('db')
                                      }
                                    }
                                    То есть это полезно, когда делал несколько отдельных проектов, и решил их все объединить в единое АПИ, но не переписывать код.

                                    Можно я рядом создам топик про сервера для Counter Strike?
                                    Если будет на js, я бы почитал.
                                    Евгений Борисов
                                    14 августа 2019, 11:10
                                    +1
                                    Как же с тобой тяжело вести диалог. Фраза агрегирование нескольких API в один это разве совсем не то, что ты мне пытаешься объяснить?

                                    Если будет на js, я бы почитал.
                                    github.com/VadimDez/Counter-Strike-JS
                                    Fi1osof
                                    14 августа 2019, 11:15
                                    -1
                                    Фраза агрегирование нескольких API в один — это описание задачи. А forwardTo — это конкретное средство решение данной задачи.
                                    Евгений Борисов
                                    14 августа 2019, 11:23
                                    0
                                    Ну раз ты предлагаешь поговорить за технические реализации конкретных задач. И тебя не смущает, что modx.pro в первую очередь сообщество о MODX, то жду от тебя решение кейса по GraphQL с ограничением доступа к различным EndPoint и полям с данными внутри объекта.

                                    На мой взгляд, агрегирование нескольких API в один, это задача частного уровня. А вот права доступа более чем насущная проблема, которая будет интересна большему кругу читателей.
                                    Fi1osof
                                    14 августа 2019, 17:55
                                    +1
                                    Местами повторюсь, потому что большую часть описал выше.

                                    За весь GraphQL не скажу, давно уже не работал напрямую с ним (ибо без дополнительных инструментов работать с ним тяжеловато местами). Но в призме есть возможность защитить эндпоинт, указан managementApiSecret в конфигурации docker-инстанса. www.prisma.io/docs/prisma-server/authentication-and-security-kke4/#prisma-server
                                    В таком случае для работой по АПИ придется указывать токен доступа.

                                    На счет данных внутри, тут хитрее задача (имеется ввиду, когда, к примеру, пользователю надо ограничить доступ к отдельным полям объектов в результатах запроса). Я пока универсального средства не нашел. Но пара моментов, которые я использую:

                                    1. Резолверы на отдельные поля объектов.
                                    К примеру, такая схема:
                                    type Query {
                                      users: [User!]!
                                    }
                                    
                                    type User {
                                      id: ID!
                                      username: String!
                                      password: String!
                                      email: String!
                                    }
                                    Мы можем прописать отдельные резолверы для каждого поля. К примеру:
                                    const resolvers = {
                                    
                                      /**
                                       * Получение списка пользователей
                                       */
                                      users: (source, args, ctx, info) => {
                                    
                                        return ctx.db.query.users(args, info);
                                      },
                                    
                                      /**
                                       * Резолверы для полей объекта Пользователь
                                       */
                                      User: {
                                    
                                        password: (source, args, ctx, info) => {
                                    
                                          /**
                                           * Пароль всегда пустой возвращаем
                                           */
                                          return null;
                                        },
                                    
                                        email: (source, args, ctx, info) => {
                                    
                                          const {
                                            id,
                                            email,
                                          } = source || {};
                                    
                                          const {
                                            id: currentUserId,
                                          } = ctx.currentUser || {}
                                    
                                          /**
                                           * Возвращаем емейл, только если это объект текущего пользователя
                                           */
                                          return currentUserId && id && currentUserId === id ? email : null;
                                        },
                                    
                                      },
                                    
                                    }
                                    Самое приятное в этом способе то, что это распространяется на любые выборки в любой части запроса/подзапроса. То есть даже если запросят Топики->Комментарии->КемСоздано(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
                                    Сама чистка выполняется здесь.

                                    А здесь я удаляю из схемы те методы, для которых резолверы не описаны. Это не совсем за секурность, но все же так и схема меньше получается, и видишь ты только то, для чего есть резолверы. А то намержишь кучу модулей, схема огромная, а реально резолверов десяток только.

                                    Ну и самая дыра по безопасности, это мутации вложенные. К примеру для запросов
                                    mutation {
                                        updateTopic (
                                                data: {
                                                    user: {
                                                        create: {
                                                            id: '...',
                                                            ...
                                                            sudo: true,
                                                        },
                                                    }
                                                }
                                            ){
                                            id
                                        }
                                    }


                                    Вот такое, в отличие от резолверов на чтение, где объявил в одном месте и везде это прогоняется, просто так не заткнешь. Поэтому я на автомате вычищаю все create/update/upsert из связанных моделей. Но все равно еще есть узкие места. Вот эта задача до сих пор для меня не полностью разрешенная. Хотя на горизонте начинают маячить варианты.

                                    P.S. В Go не силен пока, но эти приемы должны одинаково работать на любых имплементациях.
                                    Fi1osof
                                    14 августа 2019, 17:59
                                    0
                                    P.P.S. Жизнь-боль… Здесь, на мегапрофильном MODX-проекте (потому что MODX сам по себе офигенный, а все сторонние технологии от лукавого), нет даже автосохранения черновиков для комментариев. Писал-писал, а потом оп, сохраняю, а мне 404… При чем вот такая дичь: www.youtube.com/watch?v=kVENZ3DhR08&feature=youtu.be

                                    Хорошо хоть получилось вытащить коммент из запроса в дубле вкладки, переписывать бы поленился.
                                    Евгений Борисов
                                    14 августа 2019, 19:40
                                    0
                                    Мой запрос на этот кейс преследовал 2 цели
                                    — показать, что GraphQL это не серебрянная пуля. И на реальных проектах все может оказаться несколько сложнее.
                                    — Очередной раз подчеркнуть, что разбирая тонкости какой-то технологий, мы все дальше углубляемся в лес.

                                    Хотя, если бы мы обсуждали не абстрактную вещь в стиле «смотрите какая крутая штука есть. на ней можно делать то и се», а разбирали реализацию GraphQL хотя бы для modx_site_content + TV, то такой материал вызвал бы
                                    — намного больше интереса в сообществе
                                    — позволил бы углубиться в тонкости технологии с пользой для CMS и т.п.

                                    В Go не силен пока
                                    Про Go запрос был шутки ради. Но на всякий случай оставлю это тут github.com/gopherjs/gopherjs
                                    Fi1osof
                                    14 августа 2019, 19:56
                                    0
                                    Хотя, если бы мы обсуждали не абстрактную вещь в стиле «смотрите какая крутая штука есть. на ней можно делать то и се», а разбирали реализацию GraphQL хотя бы для modx_site_content + TV, то такой материал вызвал бы
                                    — намного больше интереса в сообществе
                                    — позволил бы углубиться в тонкости технологии с пользой для CMS и т.п.
                                    Нифига бы он не вызвал. Давно замечено, что чем проще, тем шире аудитория. А тем трем, кому реально интересно и захотел бы попытаться вникнуть, я вот еще почти год назад писал: prisma-cms.com/topics/izmenennaya-versiya-modxclub.ru-na-prisma-cms-poka-chto-eshhe-v-svyazke-s-modx-2817.html
                                    Там много материала для изучения и форвардинга запросов на MODX вместе с кукисами (для авторизации), и работа с БД напрямую (не только modx_site_content). Если кому интересно, здесь частично схема: github.com/prisma-cms/boilerplate-modx/tree/master/src/server/modules/modx/schema/api

                                    В общем, наработки были, но развивать их не стал за отсутствием надобности.
                                    Евгений Борисов
                                    14 августа 2019, 20:53
                                    +5
                                    Ты действительно считаешь, что сообщество MODX должно следить за сообществом PrismaCMS? Вот если бы подобная информация была в сообществе — она бы точно получила больший охват.

                                    В любом случае, если бы я раньше увидел этот топик, то давно бы тебе показал это youtu.be/xETUOC4M4m4
                                    И если бы обошлось без визгов, то мы бы конструктивно поговорили на тему ограничения прав доступа в GraphQL запросах. Затронули бы тему раскрытия путей /var/www/modxclub.ru/modxclub-3.0/ и еще много чего.
                                    Fi1osof
                                    15 августа 2019, 08:50
                                    0
                                    Во-первых, статья была написана на modxclub.ru изначально, и кому надо было, тот отследил и увидел.
                                    Во-вторых, про перебор паролей (и емейлов и телефонов), я знаю. Но уровень безопасности должен соответствовать ценности информации. Даже если ты там все сломаешь, пофигу. Сейчас задачи другие — нарабатывать функционал. Скажем так, я дико сомневаюсь, что ты можешь разработать такой фронт-редактор. Получается, все буду делать я, а безопасность будем делать мы? Нет. До безопасности доберусь, когда действительно понадобится (самые важные я заткнул дыры, а шифрованный пароль даже если ты получишь, тебе его еще надо будет дешифровать, тоже не простая задача).
                                    И если вдруг движок станет популярным (хоть и не факт), он open-source, найдутся и другие умельцы по безопасности, кто сделает свой вклад.

                                    P.S. Кстати, если получится все-таки подобрать пароль, покажи. А то пока что 9 символов подобрал, а там их 60…
                                    Евгений Борисов
                                    15 августа 2019, 09:54
                                    +2
                                    Вообще, я даже не удивлен подобной реакцией. Сначала ты доказываешь как дохера всего знаешь, кучу новых технологий их тонкости и т.п., а все вокруг даже близко до твоего уровня не дотягивают. Но когда тебя носом тыкаешь в твои же ошибки, ты резко даешь заднюю, переключаешься на другую тему и тут же начинаешь сыпать контр-аргументами в стиле «а ты сможешь такое написать?». Да, могу. Но какое это отношение имеет к топику, ветке комментариев и т.п. — мне вообще не понятно. Что ты пытаешься доказать и главное кому?

                                    Столько лет прошло, а ты так и не осознал мысль, что если кто-то способен найти твои ошибки, то значит этот кто-то знает больше твоего. Да, это не всегда так. Иногда это говорит о невнимательности разработчика, но этот случай явно не про тебя. Уж очень часто ты диалог заканчиваешь фразой «до безопасности доберусь, когда действительно понадобится».
                                    Fi1osof
                                    15 августа 2019, 10:52
                                    0
                                    Во-первых, я не отрицал наличие ошибки, я говорил, что меня это не волнует, я занят другими задачами.
                                    Во-вторых, ты ведешь себя как всякая учка в школе, которая считает, что ее предмет важнее других (чел увлекается химией, но учка по ИЗО ругается на него, что он плохо рисует). Еще раз повторяю: клал я на твою безопасность, ровно как на SEO и т.п. Тебе нравится заниматься ей? Занимайся. Но не думай, что все обязаны ей заниматься. И то, что ты в ней шаришь, это еще не много значит (примерно как всякую птицу не особо парит, что какая-то обезьяна лучше по деревьям прыгает).

                                    Да, могу.
                                    Как-то так...

                                    Что ты можешь? Ты даже еще не осознал что там вообще происходит, а уже может… Посмотри на свой профиль в гитхабе и на мой. Хотя бы просто по активности. Я вижу, что ты можешь копировать чужие репозитории. Ровно с тем же успехом ты можешь скопировать мои и сказать «Я смог». А наделе ты даже близко не занимаешься такими вещами, у тебя другой профиль.
                                    Евгений Борисов
                                    15 августа 2019, 12:51
                                    0
                                    Если ты несешь бред сивой кобылы считая, что пишешь прям мега позновательно. То пиши или в своем личном бложике, а потом плачься, что никто не читает. Ну или правильно реагируй на замечания. Не согласен? Ответь с аргументами, а не отговорками и перескакиванием на другие темы.

                                    Но по теме ветки комментариев. Мы начали разговор за реальные кейсы на GraphQL. Я понимаю, что тебе тема безопасности не важна, на SEO срать и т.д. Но если твой девиз хуяк-хуяк и в прод, то есть разработчики/студии которые стараются выпускать все-таки качественный со всех сторон продукт. И именно им в первую очередь будет познавательно узнать о реальных кейсах, а не каких-то абстрактных типо агрегирования нескольких API в один.

                                    Но даже разбирая реальные кейсы, вместо накидывая путей решений ты тыкаешь носом в свои обрывки кода, отправляешь смотреть проекты и наработки. Но при этом на конструктивную критику ни в каком направлении не принимаешь. Если бы мы обсуждали абстрактные пути решения, статьи с того же хабра, сторонние репозитории и т.п., то диалог мог быть бы абсолютно другим. Скорее всего, мы бы поговорили за подходы к реализации как там сделано и как можно по другому. Какие плюсы и минусы выбранных реализаций и т.д. Но нет, ты тешишь свое ЧСВ показывая именно свои нарабокти, но не готов их обсуждать в негативном ключе. При этом пытаешься ущипнуть меня тем, что я якобы ничего не способен реализовать. Но не задумывался ли ты о том, что
                                    1. GitHub для меня лишь закладки на разные репозитории. Часть активности по рабочим задачам тут gitlab.com/AgelxNash в приватных репозиториях. Часть в других приватных репозиториях на серверах проектов. Но даже если ты привык лицезреть на GitHub в профилях форки, то в моем случае эти форки скорее всего потому, что я туда PR отправлял
                                    2. Есть проекты, которые ограничены NDA. Какие-то проекты я даже не могу указывать в своем профиле как разработчик и за это в свое время была доплата, поскольку клиент параноик. Какие-то CRM системы я хоть и могу указать, но они приватные и доступны только через VPN.
                                    3. Как я уже говорил, мне совершенно не интересно тебе что-то доказывать. Поэтому демонстрировать именно тебе проекты доведенные до стадии production у меня нет ни малейшего желания.
                                    Евгений Борисов
                                    15 августа 2019, 15:53
                                    +4
                                    P.S. Кстати, если получится все-таки подобрать пароль, покажи. А то пока что 9 символов подобрал, а там их 60…
                                    gist.github.com/AgelxNash/e8bc55ea44d0798adf272269aa12ff86
                                    Fi1osof
                                    14 августа 2019, 11:21
                                    0
                                    github.com/VadimDez/Counter-Strike-JS
                                    Спасибо!
                                Сергей Шлоков
                                14 августа 2019, 08:23
                                0
                                Ну или возьмем пример из топика. Что сделал ТС? Он подцепился к API modx.pro который разработал Василий и завернул ответ в GraphQL. И тут напрашивается резонный вопрос, а зачем нам этот промежуточный слой в виде уже существующего /assets/components/extras/action.php, если у нас есть доступ к коду и можно сразу сформировать GraphQL
                                Насколько я понимаю, доступа к коду как раз и нет. У него же нет другой точки входа с описанными схемами или доступа к БД напрямую. Поэтому и пришлось юзать что дали. Или есть? )

                                И одна из них webonyx/graphql-php взята за основу для Railt
                                Хотя сам Кирилл её не хвалит )
                              Alex
                              14 августа 2019, 23:31
                              0
                              А можете что-то сказать об этой библиотеке github.com/nuwave/lighthouse? Стоит использовать или все таки курить Railt? Я просто вообще впервые решил gql пощупать. Lighthouse вроде бы тоже ничего — дока понятная, мануалы есть и на гите проект живой.
                                Евгений Борисов
                                14 августа 2019, 23:41
                                +1
                                С Railt могут быть проблемы при старте, т.к. документация не поспевает за кодом. Не знаю как дела обстоят с этим проектом, но выглядит очень интересно и масштабно.

                                Но в будущем, я все-таки думаю у Railt-а хорошие перспективы, т.к. код без жесткой завязки на фреймворк. А это значит, что его можно брать за основу не только в Laravel.
                                  Alex
                                  15 августа 2019, 00:31
                                  +1
                                  Спасибо за ответ.

                                  Да, Railt на перспективу интересный, но я вот пошарился по доке — очень много пустых разделов, мануалов хотя бы минимальных вообще нет, а я не настолько про чтобы полностью ориентироваться по исходникам. Какие-то моменты можно раскурить по коду, но не настолько. По времени дешевле взять что-то более готовое. Собственно поэтому и начал искать альтернативное решение. Этот github.com/folkloreinc/laravel-graphql как я понял умер, этот github.com/rebing/graphql-laravel что-то не очень понравился (субъективно).

                                  Возьму пока Lighthouse вроде все для старта есть, уже даже апишка получается минимальная буквально со старта.
                    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                    82