Примечание: Я не смог найти для себя одну хорошую статью по GraphQL — то статья избыточная для первичного знакомства с GQL, то не вся основа дана, то ещё что‑то. Но по отдельности в разных источниках дельные вещи были. Поэтому решил сделать свою статью, информацию для которой частично брал из удачных частей других статей, а что-то добавлял сам. Статьи‑источники перечислил в конце статьи.

В этой статье мы узнаем, что такое GraphQL и сравним его с REST API, проведём аналогии и посмотрим на главные различия. Ну а также, естественно, посмотрим, как работает GraphQL и какие у него возможности.

Что такое GraphQL и зачем он понадобился?

GraphQL — это язык запросов и серверная среда для API с открытым исходным кодом. Он появился в Facebook в 2012 году и был разработан для упрощения управления конечными точками для API на основе REST.

В 2015 году код GraphQL стал открытым, и сейчас GraphQL используют Airbnb, GitHub, Pinterest, Shopify и многие другие компании.

Когда разработчики Facebook создавали мобильное приложение, они искали способы ускорить работу. Была трудность: при одновременном запросе из различных по типу баз данных, например из облачной Redis и MySQL, приложение ужасно тормозило. Для решения задачи в Facebook придумали собственный язык запросов, который обращается к конечной точке и упрощает форму запрашиваемых данных. Особенно это было актуально для соц-сети, где много связей и запросов по связанным элементам — например, получить посты всех подписчиков пользователя X.

REST - хорошая штука, но у него есть некоторые проблемы с которыми и столкнулся Facebook:

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

  • Также в REST API каждый эндпойнт обычно соответствует определенному ресурсу, что может привести к проблемам с расширяемостью и поддержкой разных версий API. В GraphQL же существует единая конечная точка для всех запросов, и схема API определяется на стороне сервера. Это делает API более гибким и облегчает его развитие.

  • Ну а также во многих REST API при работе со связанными данными возникает проблема "N+1 запросов", когда для получения связанных данных нужно делать дополнительные запросы к серверу. GraphQL позволяет выразить связи между данными и получать все необходимые данные в одном запросе.

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

В классическом REST API, чтобы получить такие данные, пришлось бы сделать несколько запросов к серверу: один запрос к эндпойнту пользователей для получения списка пользователей и запрос к энпойнту постов, у которого мы запросим посты для всех найденных пользователей (либо вообще по одному запросу на каджого нужного пользователя — но это реже). С использованием GraphQL эта проблема может быть решена более эффективно. Можно запросить список пользователей и, одновременно, указать, что для каждого пользователя нужно получить последние посты.

Пример запроса в GraphQL может выглядеть так, где мы запрашиваем 5 последних постов пользователей:

query {
  users {
    id
    name
    posts(last: 5) {
      id
      text
      timestamp
    }
  }
}

А благодаря чему так происходит? Именно благодаря структуре GraphQL. Почему он Graph? Потому что он представляет собой структуру данных в виде графа, где узлы графа представляют собой объекты, а рёбра - связи между этими объектами. Это отражает способ организации данных и запросов в GraphQL, где клиенты могут запрашивать связанные данные, а также только те данные, которые им нужны.

Вот, например, граф, который показывает все отношения приложения соц-сети:

Как нам получить доступ к графу через GraphQL?

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

Мы можем взять, например, пользователя 1, и получить данные его подписчика. Давайте напишем фрагмент запроса GraphQL, чтобы показать, как получить к нему доступ:

query {
  user(id: "1") {
    followers {
      tweets {
        content
      }
    }
  }
}

Здесь мы просим GraphQL перейти к графу из корневого узла, который является объектом пользователя с аргументом id: 1, и получить доступ к содержимому твита подписчика.

Для начала неплохо, но давайте обсудим, какие бывают запросы в GraphQL подробнее.

Типы запросов GraphQL

Типы запросов в GraphQL сводятся к основным трём:

  • Query

  • Mutation

  • Subscribtion

Query: запросы в GraphQL

С ними мы уже немного познакомились в наших примерах раньше.

C помощью Query GraphQL получает необходимые данные с сервера. Тип запроса Query в GraphQL — аналог GET в REST. Запросы — строки, которые отправляются в теле HTTP POST-запроса.

Обратите внимание, все типы запросов в GraphQL отправляются через POST. Но это если мы говорим про обмен по HTTP, и это самый распространённый вариант. Но GraphQL также может работать и через Веб-сокеты, и через gRPC, и поверх других транспортных протоколов.

Примеры Query мы уже видели, но давайте ещё раз для закрепления: получим параметры fname и age всех пользователей:

query {
  users {
    fname
    age
  }
}

В ответ на этот запрос сервер присылает данные в формате JSON. Структура ответа соответствует структуре запроса:

data : {
  users [
    {
      "fname": "Joe",
      "age": 23
    },
    {
      "fname": "Betty",
      "age": 29
    }
  ]
}

В ответе приходит JSON с ключом data  и с ключом errors , если есть какие-то ошибки.

Вот пример ответа, в котором возникла ошибка из-за того, что у Alice в возрасте почему-то текстовое значение:

{
  "errors": [
    {
      "message": "Ошибка: Поле 'age' имеет недопустимое значение 'test'.",
      "locations": [
        {
          "line": 5,
          "column": 5
        }
      ],
      "path": ["users", 0, "age"]
    }
  ],
  "data": {
    "users": [
      {
        "fname": "Alice",
        "age": "test"
      },
      {
        "fname": "Bob",
        "age": 32
      }
    ]
  }
}

Mutation: мутации в GraphQL

С помощью мутаций можно добавлять данные в базу. Mutation — это аналог POST и PUT в REST. Вот пример кода:

mutation createUser{
  addUser(fname: "Richie", age: 22) {
    id
  }
}

Здесь создаётся мутация createUser, которая добавляет в БД пользователя с fname Richie и age 22. В ответ на этот запрос сервер присылает JSON с id записи. Ответ выглядит так:

data : {
addUser : "a36e4h"
}

Subscription: подписки в GraphQL

С помощью подписок клиент слушает изменения в БД в режиме реального времени. Под капотом подписки используют вебсокеты. Пример кода:

subscription listenLikes {
  listenLikes {
    fname
    likes
  }
}

С помощью этого запроса можно, например, получать список пользователей с именами и количеством лайков каждый раз, когда оно меняется.

Например, когда пользователь с fname Richie получает лайк, ответ будет таким:

data: {
listenLikes: {
    "fname": "Richie",
    "likes": 245
  }
}

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

Концепции в запросах GraphQL

Примеры разных запросов мы уже посмотрели, но теперь давайте разберёмся, как называть и организовывать эти элементы, которые используются в GraphQL.

Концепции, которые мы рассмотрим:

  1. Поля (Fields)

  2. Аргументы (Arguments)

  3. Псевдонимы (Aliases)

  4. Фрагменты (Fragments)

  5. Переменные (Variables)

  6. Директивы (Directives)

Поля (Fields)

Давайте посмотрим на простой запрос GraphQL:

{
  user {
    name
  }
}

В этом запросе вы видите 2 поля. Поле user возвращает объект, в котором есть другое поле, типа String.

Мы попросили сервер GraphQL вернуть объект пользователя с его именем, тут всё просто, идём дальше.

Аргументы (Arguments)

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

Пример:

{
  user(id: "1") {
    name
  }
}

Мы передаем id пользователя, но мы могли бы также передать аргумент name, предполагая, что у API есть функция для возврата с таким ответом.

У нас также может быть аргумент limit, указывающий, сколько подписчиков мы хотим вернуть в ответе.

Пример:

{
  user(id: "1") {
    name
    followers(limit: 50)
  }
}

Псевдонимы (Aliases)

Алиасы (aliases) в GraphQL используются для переименования полей в ответе запроса. Это полезно, когда вы хотите получить данные из нескольких полей с одинаковыми именами, но хотите, чтобы они имели разные имена в ответе. Вот пример запроса GraphQL с использованием алиасов:

Пример:

query {
  products {
    name
    description
  }
  users {
    userName: name
    userDescription: description
  }
}

И пример ответа:

{
  "data": {
    "products": [
      {
        "name": "Product A",
        "description": "Description A"
      },
      {
        "name": "Product B",
        "description": "Description B"
      }
    ],
    "users": [
      {
        "userName": "User 1",
        "userDescription": "User Description 1"
      },
      {
        "userName": "User 2",
        "userDescription": "User Description 2"
      }
    ]
  }
}

Так мы в ответе можем отличить имя и описание продукта от имени и описания пользователя. Помните, мы также делаем и в SQL, когда, например, нужно при джойне двух таблиц в результате различить похожие названия двух столбцов. Обычно эта проблема возникает с колонками id и name.

Фрагменты (Fragments)

Концепция фрагментов часто используется для разделения сложных требований к данным приложения на более мелкие порции, особенно когда вам нужно объединить множество компонентов пользовательского интерфейса с различными фрагментами в одну начальную выборку данных.

Пример:

{
  leftComparison: tweet(id: 1) {
    ...comparisonFields
  }
  rightComparison: tweet(id: 2) {
    ...comparisonFields
  }
}

fragment comparisonFields on tweet {
  userName
  userHandle
  date
  body
  repliesCount
  likes
}

Что происходит в этом запросе?

  1. Мы отправляем два запроса для получения информации о двух разных твитах: твит с id 1 и твит с id 2.

  2. Для каждого запроса мы создаем алиасы: leftComparison и rightComparison.

  3. И мы используем фрагмент comparisonFields, который содержит набор полей, которые мы хотим получить для каждого твита. Фрагменты позволяют нам избежать дублирования кода и переиспользовать один и тот же набор полей в нескольких местах запроса.

Вот какой ответ мы получим:

{
  "data": {
    "leftComparison": {
      userName: "foo",
      userHandle: "@foo",
      date: "2019-05-01",
      body: "Life is good",
      repliesCount: 10,
      tweetsCount: 200,
      likes: 500,
    },
    "rightComparison": {
      userName: "boo",
      userHandle: "@boo",
      date: "2018-05-01",
      body: "This blog is awesome",
      repliesCount: 15,
      tweetsCount: 500,
      likes: 700
  }
}

Переменные (Variables)

Переменные GraphQL – это способ динамического указания значения, которое используется в запросе. Как вы видели выше, мы передали наши аргументы внутри строки запроса. Мы будем передавать аргументы с переменной.

В примере мы добавили идентификатор пользователя id в качестве строки внутри запроса:

{
  accholder: user(id: "1") {
    fullname: name
  }
}

Давайте теперь добавим переменную и заменим статическое значение. То же самое можно записать как:

query GetAccHolder($id: String) {
  accholder: user(id: $id) {
    fullname: name
  }
}

{
  "id": "1"
}

Здесь GetAccHolder является именованной функцией. Полезно использовать именованную функцию, когда у вас много запросов в вашем приложении.

Потом мы задекларировали переменную $id с типом String. Ну а дальше уже также, как и в нашем изначальном запросе, но вместо фиксированного ИДшника, мы в запросе отдали переменную $id.
Ну а значения переменных передаются в отдельном блоке. В нашем случае id = 1.

Мы можем указать значение по умолчанию для переменной:

query GetAccHolder($id: String = "1") {
  accholder: user(id: $id) {
    fullname: name
  }
}

Также мы можем сделать переменную, как обязательную, добавив ! к типу данных:

query GetAccHolder($id: String!) {
  accholder: user(id: $id) {
    fullname: name
  }

Директивы (Directives)

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

Директивы помогают динамически изменять структуру и форму наших запросов с помощью переменных.

@include и @skip – две директивы, доступные в GraphQL

Пример директивы:

@include(if: Boolean) — Включить поле, если значение boolean-переменной = true

query GetFollowers($id: String) {
  user(id: $id) {
    fullname: name,
    followers: @include(if: $getFollowers) {
      name
      userHandle
      tweets
    }
  }
}

{
  "id": "1",
  "$getFollowers": false
}

Здесь $getFollowers имеет значение false, следовательно, поле имён подписчиков followers не будет включено в ответ.

@skip(if: Boolean) —  Пропустить поле, если значение boolean-переменной = true

query GetFollowers($id: String) {
  user(id: $id) {
    fullname: name,
    followers: @include(if: $getFollowers) {
      name
      userHandle
      tweets
    }
  }
}

{
  "id": "1",
  "$getFollowers": false
}

Здесь $getFollowers имеет значение true, следовательно, поле имен подписчиков followers будет пропущено, т. е. исключено из ответа.

Схема GraphQL

Чтобы работать с GraphQL на сервере нужно развернуть схему GraphQL (Schema), где прописывается логика работы GraphQL API, типы и структура данных. Схема состоит из двух взаимосвязанных объектов: TypeDefs и Resolvers.

Выше были описаны основные типы GraphQL. Чтобы сервер мог с ними работать, эти типы необходимо определить. Объект typeDef определяет список типов, которые доступны в проекте. Код выглядит так:

const typeDefs= gql`
  type User {
    id: Int
    fname: String
    age: Int
    likes: Int
    posts: [Post]
  }

  type Post {
    id: Int
    user: User
    body: String
  }

  type Query {
    users(id: Int!): User!
    posts(id: Int!): Post!
  }
  type Mutation {
    incrementLike(fname: String!) : [User!]
  }

  type Subscription {
    listenLikes : [User]
  }
`;

В примере выше определяется тип User, в котором указываются fnameagelikes и другие данные. Для каждого поля определяется тип данных: String или Int. GraphQL поддерживает четыре типа данных: StringIntFloatBoolean. Если в поле указан восклицательный знак, оно становится обязательным.

Также в примере выше определяются типы Query,Mutation и Subscription.

Первый тип, который содержит внутри себя тип Query, называется users. Он принимает id и возвращает объект с данными соответствующего пользователя. Это обязательное поле. Ещё один тип Query называется posts. Он устроен так же, как users.

Тип Mutation называется incrementLike. Он принимает параметр fname и возвращает список пользователей.

Тип Subscription называется listenLikes. Он возвращает список пользователей.

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

Resolver или распознаватель — функция, которая возвращает данные для определённого поля. Resolver’ы возвращают данные того типа, который определён в схеме. Распознаватели могут быть асинхронными. С их помощью можно получать данные из REST API, базы данных или другого источника.

Определим Resolver’ы:

const resolvers= {
  Query: {
    users(root, args) {return users.filter(user=> user.id=== args.id)[0] },
    posts(root, args) {return posts.filter(post=> post.id=== args.id)[0] }
  },

  User: {
    posts: (user)=> {
return posts.filter(post=> post.userId=== user.id)
    }
  },

  Post: {
    user: (post)=> {
return users.filter(user=> user.id=== post.userId)[0]
    }
  },
  Mutation: {
    incrementLike(parent, args) {
      users.map((user)=> {
if(user.fname=== args.fname) user.likes++return user
      })
      pubsub.publish('LIKES', {listenLikes: users});
return users
    }
  },
  Subscription: {
    listenLikes: {
      subscribe: ()=> pubsub.asyncIterator(['LIKES'])
    }
  }
};

В примере выше есть шесть функций:

  • запрос users возвращает объект пользователя, соответствующий переданному id;

  • запрос posts возвращает объект поста, соответствующий переданному id;

  • в поле posts User распознаватель принимает данные пользователя и возвращает список его постов;

  • в поле user Posts функция принимает данные поста и возвращает пользователя, который опубликовал пост;

  • мутация incrementLike изменяет объект users: увеличивает количество likes для пользователя с соответствующим fname. После этого users публикуются в pubsub с названием LIKES;

  • подписка listenLikes слушает LIKES и отвечает при обновлении pubsub.

Два слова о pubsub. Этот инструмент представляет собой систему передачи информации в режиме реального времени с использованием вебсокетов. pubsub удобно использовать, так как всё, что касается вебсокетов, вынесено в отдельные абстракции.

Чем концептуально хорош GraphQL

  • Гибкость. GraphQL не накладывает ограничений на типы запросов, что позволяет использовать его как для традиционных CRUD-операций (create, read, update, delete), так и для запросов, которые содержат несколько типов данных.

  • Определение схемы. GraphQL автоматически создаёт схему для API. А за счёт иерархической организации кода и объектных отношений снижается его сложность.

  • Оптимизация запросов. GraphQL позволяет клиентам запрашивать только ту информацию, что им нужна. Это уменьшает время ответа от сервера и количество данных, которые нужно передавать по сети.

  • Контекст. GraphQL учитывает все детали запросов и ответов, что позволяет разработчикам фокусироваться на бизнес-логике. А строго типизированные поля предупреждают разработчиков об ошибках перед выполнением запроса.

  • Расширяемость. GraphQL позволяет разработчикам расширять схему API и добавлять новые типы данных. При этом есть возможность повторного использования существующего кода и источников данных, чтобы избежать случаев избыточного кода.

Дополнительные ресурсы

GraphQL — Официальный сайт

Learn GraphQL — GraphQL руководства

Источники:

Комментарии (57)


  1. i360u
    03.10.2023 13:41
    +15

    Потому что он представляет собой структуру данных в виде графа, где узлы графа представляют собой объекты, а рёбра - связи между этими объектами.

    GraphQL не представляет собой структуру в виде графа. Он представляет собой дерево, со всеми нюансами в виде избыточности взаимной вложенности сущностей.

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

    Позволяет запрашивать, вот только может сильно увеличить время ответа сервера. Это как с прямыми SQL запросами. Много сложности тут перекладывается на плечи разработчика сервера. То есть никак не отменяет необходимости мероприятий по оптимизации. А еще открывает кучу дыр, которые потом требуется затыкать.

    Я даже как-то делал свой GraphQL для одной энтерпрайз-экосистемы, где использовать именно GraphQL было нельзя по многим причинам. Мое решение было основано на JSON и умело почти все тоже самое, что умеет слой обработки GraphQL, включая типизацию. И вот что я вам скажу: если вы делаете что-то массовое, GraphQL сильно поднимает требования к уровню разработчиков.

    В статье сильно не хватает секции о том, где GraphQL применим, а где лучше держаться от него подальше.


    1. nikis05
      03.10.2023 13:41
      +1

      По моему опыту, лучшее применение для GraphQL - это внутреннее непубличное API. Гибкость GraphQL позволяет фронтендерам меньше дергать бэкендеров когда надо немного поменять интерфейс. На этапе сборки и деплоя написанные фронтендерами query файлы добавляются в «белый список», и в продакшене сервер работает уже практически как обычный REST, который на конкретные вызовы из ограниченного массива разрешенных присылает конкретные ответы с конкретной схемой. И волки сыты, и овцы целы, и разработка ускоряется, и никто не положит сервер неадекватно вложенным запросом.

      А вот для публичных API GraphQL лучше реализовывать как промежуточный слой, который под капотом вызывает обычный REST. Если API небольшое, то смысла в GraphQL особо нет.


    1. vshvydky
      03.10.2023 13:41
      -1

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


  1. pyrk2142
    03.10.2023 13:41
    +2

    Хотел бы ещё спросить, сталкивался ли кто-то с проверкой безопасности проектов с использованием GraphQL? Есть ли какие-то полноценные инструкции, рекомендации или неочевидные моменты, на которые стоит обратить внимание?


    1. olku
      03.10.2023 13:41
      +2

      Нужно контролировать сложность запроса, вводим весовые коэффициенты. Нужно работать с бинарными данными, делаем ещё и REST. Нужно разные права, делаем вложенные ролевые резольверы или несколько GraphQL апишек. Как заметили в комментарии выше, требования к уровню разработчика повышаются.


      1. vshvydky
        03.10.2023 13:41

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

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

        Условно

        queryA getObj { obj { structure, meta } <--- вы

        query B getContent(structureId: ...) { content ....}

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


  1. nin-jin
    03.10.2023 13:41
    +1

    REST - хорошая штука, но у него есть некоторые проблемы с которыми и столкнулся Facebook:

    • Во-первых, это избыточность или недостаток данных в ответе

    • Также в REST API каждый эндпойнт обычно соответствует определенному ресурсу

    • для получения связанных данных нужно делать дополнительные запросы к серверу

    Ну конечно: HARP, OData, JSON:API.


    1. shaggyone
      03.10.2023 13:41
      +1

      json api сделали возможность лимитирования количества запрашиваемых вложенных объектов?


      1. nin-jin
        03.10.2023 13:41

        В HARP сделали.


        1. shaggyone
          03.10.2023 13:41
          +1

          Я правильно понимаю, что вы автор этого проекта?


          1. nin-jin
            03.10.2023 13:41
            +2

            Конечно.


            1. shaggyone
              03.10.2023 13:41

              Вы так смело свой инди проект поставили в один ряд с известными проектами. При этом вас там двое разработчиков, и возраст проекта, если считать по коммитам, год и два месяца с хвостиком.

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


              1. nin-jin
                03.10.2023 13:41

                Так запилите на python, если вам надо, какие проблемы? С этим справится любой джун на собеседовании.


                1. shaggyone
                  03.10.2023 13:41

                  У меня нет проблем, под мои задачи graphql подходит. Для задач, где он не подходит я скорее буду рассматривать зрелые проекты, где есть команда разработчиков, вокруг которых есть сообщество. Ваш проект этими вещами пока похвастаться не может.


                  1. nin-jin
                    03.10.2023 13:41

                    А, так у вас проблема в другой плоскости, - думаете лишь чужой головой, от того и не видите своих проблем. Предложенное выше надругательство на gql возвращающее проблему 1+n для решения проблемы оценки сложности, довольно красноречиво.


                    1. shaggyone
                      03.10.2023 13:41

                      Что именно из того что я говорил возвращат проблему n+1 запроса?


                      1. nin-jin
                        03.10.2023 13:41

                        мой путь - разрез графа, где я сознательно из запроса А не пропускаю ниже определенного слоя вложености.

                        И потом получаете n вызовов:

                        getContent(structureId)


                      1. shaggyone
                        03.10.2023 13:41

                        Вы хоть посмотрите, кто автор процитированного куска.


                      1. nin-jin
                        03.10.2023 13:41

                        Шо там абракадабра, шо там.)


                      1. shaggyone
                        03.10.2023 13:41

                        Те вас не парит, что вы мне предьявили претензию за слова сказанные души человеком? Ладно, через пару лет расскажете об успехах проекта.


                      1. nin-jin
                        03.10.2023 13:41

                        Меня мало чего парит. Но поздно пить боржоми, когда почки отвалились.


                    1. shaggyone
                      03.10.2023 13:41
                      +1

                      Если вы про лимитирование количества связанных объектов в вопросе про json:api, так то что если ребята из нетфликса (они вроде в этот протокол вкладывались) на этот счёт не заморочились, ещё не говорит о том, что это невозможно. Посмотрите как сделано в hasura или postgraphile, и я ещё что-то подобное как то рассматривал.

                      Я вам тоже не про код ваш говорю. Я вам говорю, что вы всесто развития сообщества пишете "вон в graphql плохо тем то", я не спорю хотя есть relay построенный на graphql, который вашу таблицу со сравнением дополняет.

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


                      1. nin-jin
                        03.10.2023 13:41
                        -1

                        Зачем мне ваше вкусное яйцо, когда я уже привык есть не вкусных куриц?


              1. flancer
                03.10.2023 13:41

                А чем python для веб-проектов лучше PHP, или Ruby, или Java, или C#, или Go, или Rust? На JS/TS можно написать и фронт, и бэк. Более того, можно написать код, который будет работать и на фронте, и на бэке. Так зачем тянуть в проект другие языки, когда можно обойтись одним? Старина Оккам бы не одобрил такое.


                1. shaggyone
                  03.10.2023 13:41

                  Заметьте, я не про "python" не, а про "условный python" говорю, разница существенная. Если авторы хотят, чтобы проект развивался, значит поддержка других ЯП будет плюс, привлечёт больше энтузиастов и т.д. и т.п. Пусть даже поддержка в формате "вот Вася Пупкин запилил поддержку на байткоде для ZX80" она проекту только в плюс пойдёт.


                  1. flancer
                    03.10.2023 13:41

                    Так и я о том же. Для разработки веб-приложений, имеющих UI в браузере, достаточно одного языка и для бэка, и для фронта. Зачем делать платформу для двух-трёх-... языков? Это не даст популярности среди "не-веб" разрабов, но заберёт имеющиеся ресурсы. А веб-разрабы уже умеют в JS/TS.

                    Пусть даже поддержка в формате "вот Вася Пупкин запилил поддержку на байткоде для ZX80" она проекту только в плюс пойдёт.

                    Я бы не хотел в своём проекте отдуваться за кривую реализацию Васи. Более того, я рекомендую всегда оценивать границы применимости инструмента перед его применением. А так-то и молотком можно шуруп "закрутить".


                    1. shaggyone
                      03.10.2023 13:41

                      1. Вы так говорите, словно не существует бэкэндов написанных на ruby, python, php, rust, golang, perl я даже не знаю чём ещё.

                      2. 2. Речь не про "вы будете поддерживать", а то что вокруг проекта будут люди, которые будут его развивать и поддерживать. Сейчас в репозитории обсуждаемого проекта 2 контрибьютора, один попадёт в больницу, другой уйдёт в буддистский монастырь и проект развивать будет некому.

                        Речь не о том, "что проект молодой, фу-фу-фу", все зрелые проекты били молодыми. Речь о том, что молодой проект на мой взгляд поспешно ставится в один ряд со зрелыми проектами, пусть даже зрелые проекты со своими проблемами.

                      3. Фронтэнд это не всегда то, с чем работает конечный пользователь в браузере. Я под задачу graphql запросы на баше делал с применением утилит jq и curl. А могу захотеть на условном Delphi фронтэнд написать, как бы вы не утверждали, что это не нужно.


                      1. flancer
                        03.10.2023 13:41

                        Для разработки веб-приложений, имеющих UI в браузере, достаточно одного языка 

                        Я об этом и только об этом. Если UI на Delphi, то и бэк разумно делать на нём же.


                      1. shaggyone
                        03.10.2023 13:41

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

                        В контексте обсуждаемого проекта, я соглашусь в формулировке, что искать надо скорее людей которые TS/JS умеют, когда проект достигнет "критической массы", питонисты, рубисты и прочие товарищи к нему сами подтянутся.


                      1. flancer
                        03.10.2023 13:41

                        А, всё, понял. Вы про HARP и только про HARP говорили, а я про $mol в целом. Да, конечно, если развивать именно HARP, то тут нужно максимальное покрытие по различным ЯП. Согласен.


                      1. shaggyone
                        03.10.2023 13:41

                        В целом да. Скорее даже не столько про сам HARP, а HARP в контексте "Есть Я, есть Пифагор, есть Ньютон и есть Эйнтштейн".


                      1. nin-jin
                        03.10.2023 13:41

                        За денормализацию в ответах GQL можно смело отчислять такого горе-проектировщика графовых протоколов из школы. В FB что ни велосипед (привет, React и Redux), то с квадратными колёсами.


                      1. shaggyone
                        03.10.2023 13:41

                        Слушайте у gql есть проблемы они известны, вы тут не первооткрыватель. Но ваш протокол ему не конкурент и не будет, причем походу в первую очередь из за вашей позиции в обсуждениях.


                      1. nin-jin
                        03.10.2023 13:41

                        К счастью, меня проблемы мышей-кактусоедов, тоже не парят.


  1. ImagineTables
    03.10.2023 13:41

    А поддерживается ли серверными библиотеками такой юз-кейс: жёсткой схемы на сервере нет, но запросы обрабатываются единообразно? Например, можно ли просто смапить GraphQL на SQL, добавив свою авторизацию и дополнительную логику? (Для работы с произвольной базой).


    1. olku
      03.10.2023 13:41
      +1

      Просто смапить не выйдет из-за разных типов данных. Наверное, вы смотрите в сторону Object Database, где модель изначально задаётся типами GraphQL. Наподобие Mongo для JSON.


      1. mvv-rus
        03.10.2023 13:41

        Просто смапить не выйдет из-за разных типов данных.

        А если не просто, а через ORM?
        Я вот смотрю на примеры возвращаемых результатов через GraphQL и у меня перед глазами прямо встают типы DTO для Entity Framework (это ORM для C#, если кто вдруг не в курсе). Остается только прикрутить библиотеку преобразования запроса c языка GraphQL в LINQ — и можно работать с реляционной БД, не сильно при этом задумываясь.


        1. Kergan88
          03.10.2023 13:41

          Для этого же odata есть, она генерит expression, который куда угодно потом можно применять.


          1. mvv-rus
            03.10.2023 13:41

            Мы тут, вроде как, GraphQL исключительно обсуждаем, не так ли?


        1. nikis05
          03.10.2023 13:41
          +2

          Я работал с GraphQL довольно долго, пробовал разное, и вот этот подход я очень не рекомендую.

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

          Второй момент - нагрузка на БД становится непредсказуемой. Иногда может быть лучше просто сделать 10 SQL запросов, чем один запрос с 10 join’ами.


          1. mvv-rus
            03.10.2023 13:41
            +1

            Возникает соблазн сдвинуть эту задачу еще на уровень ниже, то есть на уровень БД.

            Вроде как, между уровнем БД и уровнем бизнес-логики часто бывает уровень абстрактного хранилища объектов: хранилище отображает объекты, с которыми работает бизнес-логика, на операции уровня БД. И ORM как раз этим и занимается.
            Так что сдвигать задачу на уровень БД — ее схемы и операций с ней — в общем случае совсем не обязательно. При низких требованиях к производительности можно обойтись ORM и тем самым сэкономить на персонале (а бизнес экономить любит, да), которому не нужно уметь работать на уровне БД: знать SQL и, тем более, язык хранимых процедур конкретной БД, и, тем более, читать планы выполнения запросов, думать о необходимости индексов, избегать излишних блокировок и заниматься прочими обязанностями DBA.
            Бизнес-логика же при наличии такого абстрактного хранилища в виде ORM будет работать с объектами вполне статических типов (не скажу за все ORM, но конкретно EF это более чем позволяет: это его основной режим работы).
            Все это замечательно здравствует, пока не возникают нагрузки, для которых требуется оптимизация, хоть какая-то. Но это уже другая история.

            А если вспомнить исходный комментарий ветки от ImagineTables то, безотносительно к GraphQL, схема данных желательна (ибо дисциплинирует), но, накрайняк, без нее можно обойтись (например, так обстоит дело в мире XML). Проверять же запрос на правильность формулирования можно и при его приеме на севере. Правда тогда есть риск возникновения разных представлений о схеме данных у разных людей — и вот тут-то схема и проверка по ней станет настоятельно необходимой.


            1. ImagineTables
              03.10.2023 13:41

              А если вспомнить исходный комментарий ветки от ImagineTables то, безотносительно к GraphQL, схема данных желательна (ибо дисциплинирует)

              Ну, я-то речь вёл не о соблазнах, в которые ввергают нас демоны архитектуры, а о, так сказать, свободе (она же — осознанная необходимость). Свободе от схемы, потому, что её нет by design. Понятно, что какой-то зайчаток схемы всё-таки есть, но чисто для бутстраппинга (чтобы передавать произвольный список колонок сначала нужен какой-то жёстко заданный список колонок).


              Для чего это нужно на практике? Например, для приложений типа lists.live.com. (Которое недавно релизнула Майкрософт переодев Шарика). Или для low code/no code платформ. Для всяких CRM, там такое любят. Да даже для качественно сделанного офисного органайзера это пригодится (нельзя же назвать качественным органайзер, где выбираешь из тысячи заранее заданных колонок, а свою сделать не можешь).


              Теперь, надеюсь, понятно, что «смаппить на SQL» — это просто пример, когда отсутствует схема? (Вместо неё есть только какие-то соглашения, например, о кодировании семантики типов в названиях колонок). Реально это может быть и SQL, и файлы, и даже git. (Нуачобынет, если хочется дать юзерам тип колонки «версионированный файл»).


              Общаясь с одним разработчиком, я услышал, что такие вещи нельзя делать на REST API, а нужен обязательно query language. Минимизировать запросы и так далее. А я ответил, что получается какой-то нездоровый дополнительный слой, ибо на сервере надо писать парсер. (Про себя я подумал, что знаю, где обычно заканчивается такая дорога: написанием своего SQL Blackjack Edition, нестандартного, кривого, глухого и с багами). А тут вот я вижу, что нет, люди себя сумели ограничить. И выглядит полезно. Если я правильно понял, спроектировано так, что от рутины ты избавлен и можно просто писать резолверы… или нет? Для кейса с отсутствующей схемой?


              Конечно, надо бы просто скачать https://github.com/graphql/libgraphqlparser и покрутить его, но может кто-то внесёт ясность сразу на концептуальном уровне :)


    1. shaggyone
      03.10.2023 13:41
      +1

      Гляньте на утилиты вроде hasura и postgraphile.


      1. ImagineTables
        03.10.2023 13:41

        Я пока посмотрел postgraphile, и так понял, что там из схемы БД генерируется схема GraphQL (и сервер заодно). А я говорил про случай, когда жёсткой схемы вообще нет (она динамическая).


        1. shaggyone
          03.10.2023 13:41

          Базовый ответ, нет, в graphql используется строго типизированная схема. Можно конечно прикрутить, но так можно предложить и на ассемблере веб приложения писать.


        1. shaggyone
          03.10.2023 13:41

          А что значит "динамическая схема"? В базе условные документы хранятся, и в них разный набор атрибутов? Думаю такое тоже можно сделать, можно посмотреть на то, что сделали разработчики MongoDB, т.к. монго, насколько я помню, предоставляет graphql api для базы.


          1. ImagineTables
            03.10.2023 13:41

            А что значит "динамическая схема"?

            Например, вот: https://lists.live.com/



            Это, конечно, можно сделать по фиксированной схеме. Если я правильно понимаю, на базе чего они сделали это приложение, то у них в БД как раз и используется фиксированная схема (там хранится что-то типа Int, Int2 … Int16, Varchar1, Varchar2 … Varchar16, Float1, Float2 … Float16). Но в объектной модели у них доступ к колонкам разрешён по именам, заданным юзером (что-то типа Tables["MyTable"].GetElement(0).SetFieldValue["MyIntField", 42);). Мне интересно, можно ли в таких случаях сделать транспорт между клиентом и сервером на GraphQL, а на стороне сервера малой кровью преобразовывать запросы в вызовы объектной модели (имплементируя резолверы)


            1. shaggyone
              03.10.2023 13:41

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

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

              Естественно, есть рекомендация "не удалять поля из api", если они вдруг стали не нужны, чтобы старые клиенты внезапно не ломались, и в обсуждаемой реализации она к ней прибавится "не удалять столбцы из таблиц". Но это именно что рекомендация для публичных api, а не закон.


              1. ImagineTables
                03.10.2023 13:41

                вряд ли вы на всём жизненном цикле своего проекта будете ежедневно новые столбцы добавлять

                Не «мы», а «пользователи» :) И да, именно что ежедневно. В реальности они меняют схему столь же часто, как создают документы (вместо документа они создают новую таблицу). В этом и смысл таких систем.


                Под вашу задачу можно сделать получение схемы базы данных, скажем, при инициализации, плюс набор "умных" резолверов, которые будут по схеме базы данных создавать соответствующие типы, поля

                То есть, есть смысл использовать для таких задач GraphQL? И делать всё на резолверах, а не через REST API?


                1. shaggyone
                  03.10.2023 13:41

                  Я так вам сразу не скажу. Будь у меня по работе подобная задача, я бы на пару недель в думалку закинул для размышления на фоне прочих задач, посмотрел бы на варианты, обсудили бы с коллегами, поругались бы пару раз, для порядка.

                  Технически сделать можно, даже если каждый день добавляются новые столбцы, по идее технически можно реализовать, хоть с перезагрузкой схемы по 20 раз в минуту. В конце концов Graphql это не фреймворк, а протокол. У меня основновной вопрос как правильно разруливать удаление и переименование столбцов в таблицах.


                1. michael_v89
                  03.10.2023 13:41

                  GraphQL это язык для клиентов API, он подразумевает указание в запросе конкретных названий полей. Если вы знаете, какие поля есть у сущности, и можете указать их в коде клиента, значит у вас и так есть схема. Если не знаете, тогда непонятно, как вы хотите составлять запрос.


                  Конкретно для этой задачи можно сделать более высокий уровень абстракции — будет сущность List { fields: Field[] } и Field { name: string, type: string }.


    1. flancer
      03.10.2023 13:41
      +1

      Когда-то имел какое-то отношение вот к этому проекту - https://pypi.org/project/damvitool/ Базовая идея была такая: на серверной стороне при подключении в БД (MySQL или PostgreSQL) делался анализ имеющихся таблиц и отношений между ними (foreign keys), после чего модель данных в виде JSON отправлялась на фронт и использовалась для построения запросов к базе при помощи графического интерефейса. В общем, можно было через конструктор визуализировать структуру данных произвольной базы и давать пользователю возможность составлять запросы произвольной вложенности без знания SQL.


      1. ImagineTables
        03.10.2023 13:41

        Да, это как раз пример того, когда схема заранее не задана.


        (Жаль, что на странице толком не показан UI. Ведь если цель — конечные пользователи «без знания SQL», это же становится самое важное).


        1. flancer
          03.10.2023 13:41
          +1

          Что-то типа такого предполагалось. 4 - это браузер по сущностям (таблицам), в узлах (фолдерах) - связи с другими сущностями (foreign keys). 5 - это условия связывания сущностей (таблиц) по ключам, 6 - результирующая выборка (данные).

          В основе лежала идея дать возможность операторам делать произвольные выборки данных и сохранять запросы в своих favorites в веб-приложении. Принципиальную модель сделали на php/angular, затем сделали красиво на python/smartclient (вот этот скриншот), а потом забросили.


  1. kozlov_al
    03.10.2023 13:41

    один запрос к эндпойнту пользователей для получения списка пользователей и запрос к энпойнту постов, у которого мы запросим посты для всех найденных пользователей

    А что мешает сделать эндпоинт для получения списка нужных постов с нужным набором данных, где логика будет прописана не на клиенте, а на сервере?


    1. dmitryklerik
      03.10.2023 13:41
      +1

      А что мешает сделать эндпоинт для получения списка нужных постов с нужным набором данных, где логика будет прописана не на клиенте, а на сервере?

      Если у вас маленькая команда то ничего не мешает. А вот в крупных компаниях frontend и mobile делает отдельная команда, и получается неудобно на каждую фичу просить backend команду вставить новый endpoint.

      У них там свой график и план релизов, и из-за бюрократии становится сложно согласовать изменения/просить добавлять новые фичи.

      В статье о этом не написано, но самое главное достоинство GraphQL именно в этом. Это ещё один стандартизированный QueryLanguage который позволяет получать все что нужно фронту не отвлекая бэк.


  1. vshvydky
    03.10.2023 13:41

    return users.filter(user=> user.id=== args.id)[0]

    серьезно?