JSONAPI - забудьте всё, чему вас учили в Drupal-школе!

jsonapi
В продолжение темы о headless-Drupal хочу рассказать о замечательнейшем модуле JSONAPI. Согласно официальному описанию, модуль является имплементацией спецификации JSON API для Drupal.. Что же представляет из себя эта спецификация? Во-первых, сам формат данных (структура полей). Во-вторых, формат запроса данных - фильтрация, необходимые поля, сортировки и т.д. Лично я считаю этот модуль крайне необходимым для построения headless-систем, ведь JSONAPI не только делает данные более удобными для разбора на фронтенде, но и существенно сокращает этап разработки бэкенда и экономит не только ваше время, но и ресурсы сервера. И вот почему:

- вам больше не надо настраивать отображение материалов
- вам не нужны вьюсы. Ну почти не нужны, в очень редких случаях они всё же понадобятся, но об этом ниже
- вьюсы с сортировками и фильтрами теперь тоже не нужны.

Итак, перейдём к перечислению возможностей и преимуществ модуля.

Формат данных.

Известно, что в восьмёрке в ядре есть модуль REST, который позволяет получать в json любые материалы, но есть одно "но" - структура данных там будет приблизительно такая:

{
  title: [
    value: 'My node'
  ]
}

С виду, ничего страшного, но в таком случае к значению поля обратиться можно будет только через "titlte[0].value". Конечно, всем, кто знаком с друпалом, знакома и эта запись, но по факту такие данные крайне неудобно разбирать в Javascript и тем более неудобно их формировать. JSONAPI делает эту структуру куда более понятной:

{
  "data": {
    "type": "node--my-bundle",
    "id": "2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e",
    "attributes": {
      "title": "An Example"
    },
    "relationships": {
      "uid": {
        "data": {
          "type": "user--user",
          "id": "53bb14cc-544a-4cf2-88e8-e9cdd0b6948f"
        }
      }
    }
  }
}

Как видно, данные разделены на две секции - attributes и relationships, в первой отображаются поля самой запрошенной сущности, а во второй - поля-референсы - ссылка на автора, таксономия и т.д.

Запрос нескольких сущностей

Чтобы запросить одну сущность, необходимо сделать GET-запрос на адрес вида /jsonapi/{entity_type}/{bundle}/{uuid}, например:

/jsonapi/node/article/53bb14cc-544a-4cf2-88e8-e9cdd0b6948f

Как видно, здесь необходимо оперировать uuid, то же самое касается и PATCH- и DELETE-запросов.

Однако, если мы запросим адрес /jsonapi/node/article, то получим список всех статей. Вернее не всех, а только 50 штук, т.к. в модуле JSONAPI стоит ограничение выводить не более 50 сущностей. Но этот число можно уменьшить, просто указав в запросе параметр количества сущностей на страницу:

http://example.com/jsonapi/node/article?page[limit]=10

Чтобы получить ещё десяток статей, укажем параметр offset:

http://example.com/jsonapi/node/article?page[limit]=10&page[offset]=10

Сортировка выборки

Полученную выборку можно отсортировать по любым критериям. Тут стоит отметить, что многие параметры в JSONAPI имеют две формы записи - полную и сокращённую. Например, отсортировать статьи по дате создания можно так:

SHORT
sort=created

NORMAL
sort[sort-created][path]=created

Отсортировать по имени автора в обратном порядке:

SHORT
sort=-uid.name

NORMAL
sort[sort-author][path]=uid.name
sort[sort-author][direction]=DESC

Отсортировать одновременно по нескольким критериям:

SHORT
sort=-created,uid.name

NORMAL
sort[sort-created][path]=created
sort[sort-created][direction]=DESC
sort[sort-author][path]=uid.name

Фильтрация выборки

Параметры фильтров по своей структуре аналогичны параметрам сортировки. Вот пример для получения только опубликованных материалов:

SHORT
filter[status][value]=1

NORMAL
filter[status-filter][condition][path]=status
filter[status-filter][condition][value]=1

Хочу заметить, что в данном случае в краткой форме status - это системное название поля, а в полной форме status-filter - это произвольное название фильтра.

Как вы уже догадались, с помощью фильтров можно запрашивать ноды по nid, а не по uuid.

Фильтровать можно и по полям связанных сущностей, в таком случае в качестве пути фильтра пишется название поля ссылки, а через точку добавляется название поля в целевой сущности, например так выглядит фильтрация по имени автора:

SHORT
filter[uid.name][value]=admin

NORMAL
filter[name-filter][condition][path]=uid.name
filter[name-filter][condition][value]=admin

Также есть возможность фильтровать не только по критерию равенства, доступны операторы "IN", "NOT IN", ">", "<", "<>", "BETWEEN" и "CONTAINS". Вот пример для фильтра по нескольким именам авторов:

NORMAL
filter[name-filter][condition][path]=uid.name
filter[name-filter][condition][operator]=IN
filter[name-filter][condition][value][]=admin
filter[name-filter][condition][value][]=john

Больше примеров вы найдёте здесь

Запрос связанных сущностей

По умолчанию связанные сущности не возвращаются сервером, отдаётся лишь информация о типе сущностей и их uuid в секции relationships. Однако мы можем запросить и их, просто указав параметр include в нашем запросе, например include=uid добавит в ответ сервера все поля авторов. Вернее сказать не поля, а сущности, просто они будут не в секции data, а в секции included.

Указание нужных полей

Часто нам не нужны все поля сущности. Для этого есть параметр fields. Например:

http://example.com/jsonapi/node/article/{{article_uuid}}?include=uid&fields[node--article]=title,body&fields[uid]=name

Этот запрос выдаст нам заголовок статьи, её текст и имя автора.

Итого

Как видно, Views нам действительно теперь не нужен. В принципе, теперь для сборки бэкенда на Drupal достаточно создать все нужные сущности и их поля и установить модуль jsonapi. И всё.

Что у модуля под капотом?

Говорят, что при некоторых запросах JSONAPI даже не делает полный бутстрап друпала, но я не проверял это. Как бы то ни было, по быстродействию даже на глаз видно, что модуль работает быстрее, чем views. А логирование SQL-запросов показало, что сперва делается один запрос, возвращающий айдишники сущностей, а сами сущности берутся из таблицы cache_entity. В отличие от того же views - не нужно тратить ресурсы на построение объекта самого представления.

Недостатки

- не поддерживаются обратные референсы (например, в выборке по пользователям не получится собрать их материалы)
- не поддерживается агрегация
- нельзя получить за один запрос более 50 сущностей (но существуют обходные пути - параллельные запросы либо модуль Subrequests)

Первые два недостатка порой весьма критичны, причём их никогда не пофиксят, ибо это подразумевается в самом стандарте спецификации JSONAPI. Тем не менее, с такими запросами легко справляется Views, именно поэтому я и писал, что он почти не нужен.

PS: данная статья не является переводом официальной документации, хоть и содержит выдержки из неё. Для тех, кто хочет узнать больше, рекомендую следующие ссылки:

Страница модуля: https://www.drupal.org/project/jsonapi

Официальная документация: https://www.drupal.org/docs/8/modules/json-api

Видеоуроки на YouTube: https://www.youtube.com/playlist?list=PLZOQ_ZMpYrZsyO-3IstImK1okrpfAjuMZ - это реально круто!

Спецификация стандарта: http://jsonapi.org/

Ссылка на оригинал статьи в моём блоге: http://wellsolutions.by/article/jsonapi-zabudte-vsyo-chemu-vas-uchili-v-...

Автор

Комментарии

Аватар пользователя gun_dose gun_dose 26 августа 2017 в 14:41

GraphQL, как я понял, пока не так стабилен, как хотелось бы. А ещё так и не понял, поддерживает ли спецификация GraphQL запрос обратных референсов.

Аватар пользователя bumble bumble 26 августа 2017 в 14:52

На сколько я понимаю, GraphQL это "правильный" REST API.
Суть его заключается в снятии ограничений (можно запрашивать все и вся), и более гибкий подход к работе с данными. Плюс, всего 1 эндпоинт на все запросы ))

Аватар пользователя bsyomov bsyomov 28 августа 2017 в 13:45

Правильный? =) Это, на самом деле, не более чем 100501 язык запросов. Возможно, неплохо проработанный но и, возможно, слишком универсальный - а это делает излишне сложной реализацию, обычно. Да и других подводных камней хватает - всё и вся, да ещё в произвольных выборках бывает просто не разумно отдавать, а иногда и нельзя, и вот у вас уже только подмножество стандарта...

Вообще, делать из API что-то вроде БД с возможностью произвольных запросов, идея довольно-таки плохая, в большинстве случаев - она делает несколько проще жизнь разработчиков фронтэнда/клиентских приложений, но при этом, на порядки усложняет серверную часть, создаёт массу головной боли с безопасностью, с производительностью и.т.п.

Свой же простой протокол обмена, пусть не так стандартизированный, а сделанный по каким-то общим принципам (например REST), чтобы меньше продумывать основы протокола, и использовать частично готовые наработки, минимально необходимый для работы приложения куда проще написать, отладить, использовать. И не так уж сложно после расширить.

Насколько GraphQL станет, в итоге, популярен огромный пока вопрос. Шуму вокруг него много сейчас, да, но часто, шум утихает, и технология оказывается забытой.

Аватар пользователя bumble bumble 28 августа 2017 в 14:02

Ну... Если смотреть на все проблемы с т.з. "СЛОЖНАА", то да ))) можно не писать и не учить ничего нового. Но это же не наш путь ;)

ЗЫ - граф не язык запросов, а просто спека практик формирования API. И да, он скорее для аппликух, чем для сайтиков-визиточек.

Аватар пользователя bsyomov bsyomov 28 августа 2017 в 14:33

Посыл был не в том, что сложнааа осваивать, и не надо нового, а в том, что делать сложно, не всегда уместно, и простые решения часто быстрее/надёжнее/да и просто разумнее.

ЗЫ: GraphQL is a query language for your API. Это не я придумал, это цитата с http://graphql.org/learn/ Практики построения запросов это, как раз, REST например.

Аватар пользователя bumble bumble 28 августа 2017 в 14:39

..for your API же ))

И я прошу и меня понять правильно!

Ведь я не навязываю его использование, и тоже считаю что простое - лучше. Но технологии эволюционируют, развиваются и вместе с этим - усложняются. Таковы реалии и таковы потребности современного веба.

Поэтому - технологии лучше знать и уметь пользоваться. Вот мой посыл.

Аватар пользователя bsyomov bsyomov 28 августа 2017 в 15:09

И всё же, по сути, является он именно языком запросов, да? =)

На мой взгляд, надо не гоняться за модой, а думать, какая технология хорошо решит ту или иную проблему, и именно из этого исходить в своём выборе.
Вокруг GraphQL сейчас очень много хайпа, прежде всего из-за того, что его планирует внедрить Github, и многие в очередной раз считают, что нашли "серебряную пулю", которая решает все проблемы взаимодействия с бекэндом. Но надо мыслить рационально, и понимать, что серебряной пули просто не существует, а у каждой технологии есть своя область применения, и конечно, всегда есть недостатки.

Собственно поэтому для меня, как раз, не так очевидно, что следующий шаг должен быть в эту сторону, и уж конечно, для меня утверждение GraphQL это "правильный" REST API не верно. =) И поэтому-то я и написал свой комментарий.

По поводу усложнения технологий, на мой взгляд, это не совсем верно - да, они изменяются. Где-то усложняются, где-то упрощаются. Например, всё популярнее движки, генерирующие статический контент. Или микросервисные архитектуры, использующие очень простые компоненты, выполняющие минимум действий.

Надеюсь, нас gun_dose простит за оффтоп. =)

Аватар пользователя bumble bumble 28 августа 2017 в 15:31

Да, соглашусь - язык запросов верно (даже в названии ведь "QL"). Я просто в обычном смысле ассоциировал с языком типа SQL, это вызвало у меня диссонанс. ))

И про использовать как лекарство от всех болячек - тоже согласен.

А про "следующий шаг" - я писал конкретно в контексте ТС.
Т.е. предположив что его следующий шаг будет в эту сторону.

Про "движки, генерирующие статический контент" и "микросервисные архитектуры, использующие очень простые компоненты, выполняющие минимум действий" - это как раз и есть "усложнение" технологий:

  • Генераторы статики - уже не просто генерируют контент и отдают странички (препроцессор гипертекста), а выполняют множество задач помимо (хранение, обработка, выдача).
  • Микросервисы, в свою очередь, и есть те "усложняющие" механизмы. Вместо 1го слитного приложения - собираем из кучки модулей то что нужно.

Также, обращаю внимание на выделенные кавычками слова про "услажнятели" - я их употребляю не в значении "технологии использовать сложно", а в значении "технологии становятся более взрослыми и самостоятельными".

Аватар пользователя multpix multpix 28 августа 2017 в 17:02

@bumble, @bsyomov
его можно рассматривать как еще один уровень абстракции при запросе данных.

Думаю, сие очень понравится разработчику клиентского приложения.
Возможность работать со множеством источников данных как с единым целым,
что-то быстрое дергается из redis, часть данных в бд, и т.д.,
а разработчик просто пишет запрос.

Аватар пользователя gun_dose gun_dose 28 августа 2017 в 14:17

GraphQL не разбирал пока, но в друпаловской реализации JSONAPI запросы вовсе не произвольные - все права доступа проверяются.

Аватар пользователя gun_dose gun_dose 28 августа 2017 в 21:15

Кстати, некоторые называют jsonapi облегчённой версией graphql, идеологически оно так и есть. Что же касается "усложнения" сайта, то хочу заверить, что jsonapi - это огромный плюс к производительности сайта. Вот.)))

Аватар пользователя bsyomov bsyomov 30 августа 2017 в 4:35

Ну нет - jsonapi это описание формата обмена, и не заставляет тебя реализовать ответ на произвольные запросы. Всё по классике - есть эндпоинты, есть операции с ними, ты предоставляешь доступ к чётко ограниченному API. Тут нет сложной прослойки, и всё просто, и предсказуемо. А т.к. не надо дополнительно шаблонизировать на стороне сервера, и формат передачи куда компактнее HTML, то да, всё может быть и быстрее.

Вся сложность реализации GraphQL как раз в том, что тебе может придти довольно "сложный" запрос, к ответу на который, ты можешь быть совсем не готов. Например, структура твоих данных может быть такова, что извлечение запрошенной информации будет адски не эффективно... И ты не можешь даже предсказать, какой из запросов с фронтэнда/клиентского приложения/etc положит, в итоге, твой бекэнд. Всё возможные варианты запросов заранее не проверить. =) Т.е. ты получаешь весьма сложный кусок кода, который до кучи, очень сложно отлаживать из-за большого множества возможных входных данных. А когда к этому надо привернуть проверку прав доступа, да ещё и более-менее гранулярную, то вообще могут разверзнуться врата ада. =)

При этом польза, в основном, в том, что фронтэндеру не надо внимательно читать документацию по твоему API, и надо знать несколько меньше инструментов, чтобы работать с твоими данными... Оправдано это? Обычно нет, если ты не Facebook, или Github.

Т.е. для того, чтобы оторвать голову сайту(а ведь это мы тут обсуждаем, прежде всего), это не самый разумный путь, прямо скажем, в отличии от того же jsonapi.