×

Введение в GraphQL: что это за язык и как использовать его под Android

Всем привет! Меня зовут Мария Агеева, я Android-разработчик. Около 2 лет работаю с GraphQL. Хотела бы поделиться опытом работы с этой технологией и, возможно, заинтересовать в ее использовании тех из вас, кто еще с ней не знаком, собирается ее использовать или только начал интеграцию GraphQL в проект. Также в статье будет кратко описана работа с GraphQL для платформы Android.

Что такое GraphQL

Прежде всего рассмотрим, что же такое GraphQL. По определению с официального сайта, GraphQL — это язык запросов и манипулирования данными для API, а также среда для выполнения этих запросов. Язык был разработан в 2012 году в Facebook для внутренних нужд компании, в 2015-м вышел в открытый доступ, а с 7 ноября 2018 года работу над ним ведет не Facebook, а GraphQL Foundation. Конечно, проект развивался достаточно активно с 2012 года, но особую популярность заработал после того, как получил статус open source.

Даже если ранее вы никогда не слышали о GraphQL, велик шанс, что вы знаете или использовали продукты, которые написаны с его применением. Во-первых, это, естественно, социальная сеть Facebook. Кроме нее, с GraphQL работают при разработке таких продуктов, как Airbnb, GitHub, Pinterest, Shopify, New York Times и многих других.

Вернемся к созданию языка. То, что он был создан именно в Facebook для проекта с большим объемом разнородных данных, говорит о том, что при работе над подобным продуктом могут возникать ситуации с ограничениями REST-архитектуры. Например, получение профиля пользователя, его постов и комментариев изначально не кажется сложной задачей. Но если учесть объем данных в системе и предположить, что все эти данные хранятся в разных базах данных (например, MySQL и MongoDB), становится понятно, что для этого понадобится создать несколько REST-endpoint’ов. Ну а если представить, насколько велик объем данных и разнородны источники данных, то становится понятно, почему понадобилось разрабатывать новый подход к работе с API. В основе этого подхода лежит следующий принцип: лучше иметь один «умный» endpoint, который будет способен работать со сложными запросами и возвращать данные именно в той форме и в том объеме, которые необходимы клиенту.

В центре любой имплементации GraphQL API лежит схема данных — это описание того, с какими типами данных он может работать и какие типы данных может вернуть в ответ на запрос (они описаны в системе типов GraphQL). Проще говоря, для работы с любым API пользователю необходимо знать, какие типы объектов можно получить, какие поля выбрать, какие поля доступны во внутренних объектах и т. д. Именно в этом нам поможет схема.

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

Взаимосвязь клиента и сервера при работе с GraphQL

Тут я хотела бы остановиться на том, как, собственно, устроена работа клиента и сервера при использовании GraphQL. Так как я не back-end-разработчик, то расскажу только вкратце о работе с серверной частью, не вдаваясь в подробности.

Схема взаимодействия клиента и сервера GraphQL

Работу с GraphQL поддерживает сейчас большое количество платформ: веб, Android, iOS и многие другие. GraphQL-клиент отправляет запрос на получение данных или на их изменение, составленный в соответствии со схемой, на GraphQL-сервер. GraphQL-сервер, в свою очередь, представляет собой HTTP-сервер, с которым связана схема GraphQL. То есть имеется в виду, что через эту схему «пропускаются» все запросы, полученные от клиента, и возвращаемые ответы.

Сервер GraphQL не может знать, что делать с запросом, если ему не «объяснить» это с помощью специальных функций. Благодаря им GraphQL понимает, как получить данные для запрашиваемых полей. Эти функции связаны с соответствующими полями и называются распознавателями, или резолверами (resolvers). После этого клиенту возвращается ответ, который отражает запрашиваемую с клиента структуру данных, обычно в формате JSON. И, повторюсь, тут можно работать с абсолютно различными источниками данных: базы данных (реляционные/NoSQL), результаты веб-поиска, Docker и т. д.

Сравнение GraphQL API и REST API

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

  • Они предоставляют единый интерфейс для работы с удаленными данными.
  • Чаще всего на запрос возвращается ответ в формате JSON.
  • Оба подхода позволяют дифференцировать запросы на чтение и на запись.

Но у GraphQL есть и много преимуществ по сравнению с REST:

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

На практике использование GraphQL увеличивает независимость front-end- и back-end-разработчиков. После согласования схемы front-end-разработчики больше не будут просить создать новые endpoint’ы или добавить новые параметры для имеющихся: back-end-разработчики один раз описывают схему данных, а front-end-специалист создает запросы и комбинирует данные так, как это необходимо.

Система типов

В основе любого GraphQL API лежит описание типов, с которыми можно работать и которые он может вернуть — как было сказано ранее, схема. Так как сервисы GraphQL могут быть написаны на многих языках, то был разработан универсальный GraphQL schema language. Рассмотрим основные типы данных, которые он поддерживает.

Объектные

Наиболее базовые типы GraphQL — объектные типы, которые представляют собой объект и набор полей, описывающих его.

Все примеры в этой статье я буду приводить с использованием Star Wars API.

Например:

type Planet {
    id: ID!
    diameter: Int
    name: String!
    population: Float
    residents: [Person!]
}

Скалярные

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

  • Int — 32-битное целое число со знаком;
  • Float — число двойной точности с плавающей точкой со знаком;
  • String — строка в UTF-8;
  • Boolean — логический тип (true или false);
  • ID — специальный скалярный тип, представляющий собой уникальный идентификатор, который используется чаще всего для получения объекта или как ключ в кеше. Значения типа ID сериализуются так же, как и String, но тот факт, что для идентификаторов был выделен отдельный тип данных, говорит о том, что он должен использоваться не для отображения клиенту, а только в программах.

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

Хотелось бы еще отметить, что в GraphQL можно добавить и так называемые модификаторы типов (type modifiers), которые влияют на валидацию полей. Очень распространен non-null модификатор, который гарантирует, что данное значение никогда не будет null (иначе получим ошибку выполнения). Обозначается как !, например:

    id: ID!

Другой распространенный модификатор — List. Можно пометить тип как List, в таком случае ожидается, что в этом месте будет возвращен массив из таких значений. Обозначается как [], например:

    films: [Film!]

Модификаторы можно комбинировать и использовать на любом уровне вложенности.

Аргументы

Представляют собой набор пар «ключ — значение», которые привязаны к определенному полю. Они передаются на сервер и влияют на то, как будут получены данные для определенного поля. Аргументы могут быть и литералами, и переменными. Их можно применять на любых полях вне зависимости от уровня их вложенности. Они обязательно должны быть именованными, а также могут быть обязательными или опциональными (если аргументы опциональные, то их значение должно быть задано по умолчанию). По типу данных значения аргументов могут быть скалярными или специальными объектными input-типами.

Например, тут к полю Film привязан аргумент id (в данном примере литерал типа ID):

query {
    Film(id:"1234abcd") {
        id
        title
        characters {
            name
        }
    }
}

Перечисления

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

enum HAIR_COLOR {
    BLACK
    BLONDE
    BROWN
    GREY
}

Также в GraphQL есть 2 абстрактных типа: union и interface. Они могут использоваться только как возвратный тип, то есть можно лишь получить данные такого типа, но не передать в запрос в качестве аргумента.

Интерфейсы

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

interface Character {
    id: ID!
    name: String!
}
type Droid implements Character {
    id: ID!
    name: String!
    function: String
}
type Human implements Character {
    id: ID!
    name: String!
    starships: [Starship]
}

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

union SearchResult = Human | Starship | Film
... on Human {
    name
    height
}
... on Starship {
    model
    capacity
    manufacturer
}
... on Film {
    title
    episode
}

Можно задать вопрос: зачем использовать 2 абстрактных типа? Потому что у этих типов принципиально разное применение. Union-типы используются в тех местах схемы, где можно сказать, что тут можно вернуть один из перечисленных типов. В свою очередь, интерфейсы используются там, где о типе можно сказать, что он реализует этот контракт.

Query, mutations, subscriptions

Cуществуют специальные типы, определяющие тип операции, которую клиент хочет выполнить, например получение данных или их изменение. Эти типы являются точками входа для API. Любой GraphQL API должен обязательно иметь хотя бы один query, но mutations и subscriptions необязательны. Стоит отметить, что, несмотря на свой особый статус, эти специальные типы такие же, как и другие объектные типы GraphQL. Рассмотрим каждый из этих типов подробнее.

Query. Cпециальный тип, который по конвенции представляет собой запрос на получение данных (можно сказать, что это аналог GET в REST). Именно этот тип данных обязателен для любого GraphQL API. Рассмотрим простой пример:

query GetAllFilms {
    allFilms {
        id
        title
        episode
    }
}

Это простой запрос на получение информации обо всех фильмах (как понятно из названия). Уже на этом этапе видно важное преимущество использования GraphQL: мы получаем только те данные, которые нам нужны в приложении (в данном случае id, название и номер эпизода фильма). Таким образом, на практике не приходится на клиенте получать все данные независимо от того, нужны они или нет, и выполнять порой достаточно сложную фильтрацию, если можно получить лишь нужные данные и далее работать только с ними. Для дебага и логирования также полезно именовать запросы (при работе с некоторыми технологиями, например при разработке для Android, анонимные query не поддерживаются в принципе).

Mutations. Cпециальный тип, который, в отличие от query, используется для изменения данных (можно сказать, что это аналог POST). Да, конечно, это рекомендация, и на практике не запрещено использовать для изменения данных и query, но все же так делать не стоит. Ведь основное отличие query от mutation — их поведение при получении результатов запросов: при выполнении query обработка полей результата будет выполняться параллельно, так как они не изменяют данные. В свою очередь, в mutations это выполняется последовательно, чтобы обеспечить целостность данных. Пример:

mutation DeleteEpisode {
    deleteFilm(id: “123456”) {
        title
    }
}

В примере можно заметить, что отличий в синтаксисе нет, запрос начинается с названия операции (mutation) и также является именованным.

Subscriptions. Cамый новый специальный тип данных, который позволяет реализовать систему publisher/subscriber. То есть это способ отправки данных клиенту, который «слушает» эти данные в реальном времени. В отличие от предыдущих 2 специальных типов, тут ответ сервера приходит не единоразово, а каждый раз при срабатывании определенных условий на сервере при условии, что клиент «слушает» эти данные. Можно сказать, что это аналог push-нотификаций, которые работают в одной системе со всеми другими типами запросов — query и mutations. Большой плюс данного подхода — то, что клиент и в этом случае может с помощью аргументов определить, при каком условии результат с сервера должен приходить на клиент. Пример:

subscription WaitForNewFilm {
    Film(filter:{ action:CREATED }) {
        id
        title
        episode
        characters {                
            name
        }
    }
}

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

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

Introspection query и GraphQL Playground

Для получения информации обо всех типах, которые поддерживает данный GraphQL API, можно выполнить так называемый introspection query. По умолчанию на дебаг-версии сервера этот запрос доступен, на production, естественно, должен быть отключен. Также работу с API упрощает использование GraphQL Playground — это графическая интерактивная среда разработки, базирующаяся на GraphiQL, полноценной IDE для работы с GraphQL. При разработке и использовании Apollo Server, распространенного open source — сервера для разработки с использованием GraphQL, Playground становится доступным на том же endpoint, что и сервер (например, localhost:4000/playground). И, как и в случае с introspection query, он доступен только в дебаг-версии.

Работа с GraphQL API при разработке для Android

В заключительной части своей статьи я хотела бы кратко описать работу с GraphQL для Android-разработки. Наиболее распространенный сервис для работы с GraphQL — Apollo GraphQL Server. Это open source — проект, который активно развивается Apollo GraphQL. Версии его существуют для многих платформ, в том числе для веб, iOS и Android. И именно о последнем пойдет речь далее.
Apollo GraphQL клиент для Android поддерживает многие из основных функций на данном этапе, в том числе:

  • queries, mutations, subscriptions;
  • нормализованный кеш;
  • загрузку файлов;
  • собственные скалярные типы.

В основе его работы — кодогенерация сильно типизированных моделей по схеме GraphQL. По умолчанию поддерживается кодогенерация на Java, но можно в качестве экспериментальной фичи использовать Kotlin-кодогенерацию.
Для облегчения работы с GraphQL в Android Studio хорошо подходит плагин JS GraphQL. Основные его плюсы:

  • подсветка синтаксиса, автодополнение, форматирование кода;
  • возможность выполнения introspection query в Android Studio;
  • поддержка выполнения query, mutations в Android Studio и др.;
  • go-to-definition для GraphQL-файлов.

Рассмотрим, как же подключить поддержку GraphQL в проект. Последняя версия клиента на момент написания статьи — 1.4.4.

В build.gradle уровня приложения необходимо добавить следующие строки для работы с Gradle-плагином Apollo:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
       classpath("com.apollographql.apollo:apollo-gradle-plugin:x.y.z")
    }
}

В build.gradle уровня модуля добавляем следующее:

apply plugin: 'com.apollographql.apollo'
repositories {
    jcenter()
}
dependencies {
    implementation("com.apollographql.apollo:apollo-runtime:x.y.z")
// If not already on your classpath, you might need the jetbrains annotations
    compileOnly("org.jetbrains:annotations:13.0")
    testCompileOnly("org.jetbrains:annotations:13.0")
}

Далее необходимо создать директорию в проекте (модуле), например src/main/graphql/com/my_package/, в которую следует добавить файл schema.json — схему GraphQL в формате JSON, полученную в результате выполнения introspection query того API, который будет использоваться в проекте.

В этой же директории создаем файлы с запросами в формате .graphql, например queries.graphql. Единственный нюанс при создании запросов — запросы (query, mutations, subscriptions) должны быть именованными.

По умолчанию, как упоминалось выше, используется кодогенерация моделей на Java. Если же нужно включить Kotlin-кодогенерацию, добавляем следующее:

apollo {
    generateKotlinModels.set(true) 
}

И затем выполняем команду./gradlew generateApolloSources, чтобы модели по запросам из директории src/main/graphql/com/my_package/ были сгенерированы. При создании моделей на основе GraphQL-типов будут сгенерированы Java-классы (или Kotlin-классы), типы полей классов соответствуют скалярным типам GraphQL. При использовании типа ID в GraphQL в сгенерированном классе используется тип String. При необходимости также можно добавить собственные скалярные типы и определить то, как они преобразуются (их маппинг).
Для работы с сервером (например, для выполнения запросов и их конфигурации, настройки subscriptions) и работы с кешем используется класс ApolloClient. Пример его создания и конфигурации:

val apolloClient : ApolloClient = ApolloClient.builder()
    .serverUrl(ENDPOINT)
     .subscriptionTransportFactory(
         WebSocketSubscriptionTransport.Factory(SUBSCRIPTIONS_ENDPOINT, okHttp))
    .okHttpClient(okHttp)
    .build()

Теперь рассмотрим, как же можно выполнить основные типы операций GraphQL. Клиент Apollo GraphQL поддерживает и стандартное выполнение операций с использованием callback-функций, и RxJava2 и coroutines, для чего предполагается подключение отдельных зависимостей Gradle.

Предположим, что мы хотим выполнить 2 запроса с одним и тем же типом данных GraphQL Film и выбором одних и тех же полей в нем:

query GetFilmNotOptimized($id:ID!) {
     Film(id:$id) {
        id
        title
        director
        episodeId
        planets {
            id
            name
            diameter
            population
            climate
        }
    }
}
query GetFilmsNotOptimized {
    allFilms {
        id
        title
        director
        episodeId
        planets {
            id
            name
            diameter
            population
            climate
        }
    }
}

Вот как это будет выглядеть в проекте. Первый запрос:

apolloClient.query(
    GetFilmNotOptimizedQuery.builder()
        .id(id)
        .build()
).enqueue(object : ApolloCall.Callback<GetFilmNotOptimizedQuery.Data>() {

    override fun onFailure(e: ApolloException) {
        /* Response to exception */
    }

    override fun onResponse(response: Response<GetFilmNotOptimizedQuery.Data>) {
        val filmType = response.data()?.Film()?.__typename()
        val filmClassName = response.data()?.Film()?.javaClass?.simpleName
        Log.i("ApolloStarWars", """GetFilmNotOptimizedQuery: film type = $filmType, film Java class = $filmClassName""")
       //result: GetFilmNotOptimizedQuery: film type = Film, film Java class = Film
	}
})

И второй запрос:

apolloClient.query(
    GetFilmsNotOptimizedQuery()
).enqueue(object : ApolloCall.Callback<GetFilmsNotOptimizedQuery.Data>() {
    override fun onFailure(e: ApolloException) {
        /* Response to exception */
    }
    override fun onResponse(response: Response<GetFilmsNotOptimizedQuery.Data>) {
        val filmType = response.data()?.allFilms()?.first()?.__typename()
        val filmClassName = response.data()?.allFilms()?.first()?.javaClass?.simpleName
        Log.i("ApolloStarWars", """GetFilmsNotOptimizedQuery: films type = $filmType, film Java class = $filmClassName""")
       //GetFilmsNotOptimizedQuery: films type = Film, film Java class = AllFilm
    }
})

Тут можно заметить, что Apollo GraphQL генерирует 2 разных Java-типа на один и тот же GraphQL-тип даже при условии того, что выбраны одни и те же поля. В таком случае можно воспользоваться фрагментами GraphQL — переиспользуемыми структурами, которые позволяют определять наборы полей, основанные на каком-либо GraphQL-типе, и включать их в запросы. Например, предыдущие запросы можно оптимизировать, используя такие фрагменты:

fragment PlanetFragment on Planet {
    id
    name
    diameter
    population
    climate
}
fragment FilmFragment on Film {
    id
    title
    director
    episodeId
    planets {
        ...PlanetFragment
    }
}

Запросы, в свою очередь, будут выглядеть гораздо проще, а именно так:

query GetFilm($id:ID!) {
   Film(id:$id) {
       ...FilmFragment
   }
}

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

Поддержка RxJava2 и Kotlin Coroutines

И в заключение кратко о поддержке RxJava2 и Coroutines.

Для подключения в проект поддержки RxJava2 необходимо добавить следующий импорт в build.gradle уровня модуля:

implementation 'com.apollographql.apollo:apollo-rx2-support:x.y.z'

Он включает в себя набор extension-функций (Kotlin) и класс Rx2Apollo (Java) для конвертации ApolloCall в Observable. Вариант использования:

 apolloClient.rxQuery(
      GetFilmsQuery()
  ).singleElement()
   .toSingle()
   .map { response ->
       return@map response.data()?.allFilms()?.map { film ->
           film.fragments().filmFragment().toFilm()
       }?: listOf()
   }

Аналогично для поддержки Coroutines добавляем следующий импорт:

implementation 'com.apollographql.apollo:apollo-coroutines-support:x.y.z'

Тут включены extension-функции для конвертации ApolloCall во Flow, в Deferred и Channel.

apolloClient.query(
   GetFilmsQuery()
).toDeferred()
.await()
.data()?.allFilms()?.map { film ->
   film.fragments().filmFragment().toFilm()
}?: listOf()

Выводы

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

Более того, с моей точки зрения, если структура данных в проекте несложная, обязательный переход на GraphQL не имеет смысла, все зависит только от желания разработчиков. Но этот подход действительно упрощает работу с большими разнородными данными. Главное — помнить о том, что GraphQL не предполагает переработки всего того, что было раньше, и в итоге каждый должен выбирать тот подход, который ему ближе. Но я считаю, что GraphQL — определенно та технология, с которой стоит ознакомиться.

Мне, как Android-разработчику, очень нравится использование этой технологии по многим причинам. Это и то, что в итоге клиентское приложение работает со сгенерированными классами Java или Kotlin, и то, что нет необходимости фильтровать данные на клиенте, и возможность использовать не только запросы на получение данных и их изменение, но и subscriptions (можно сказать, аналоги push-нотификаций) в одной системе без необходимости использования и настройки сторонних сервисов и т. д.

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

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось6
До обраногоВ обраному12
LinkedIn

Схожі статті




75 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Скажіть, будь ласка, які є інструменти для написання документації по сущностям в GraphQL ?

В основе этого подхода лежит следующий принцип: лучше иметь один «умный» endpoint, который будет способен работать со сложными запросами и возвращать данные именно в той форме и в том объеме, которые необходимы клиенту.

Для решения конкретно этой задачи не нужно пилить GraphQL. Спецификация JSON:API например вполне позволяет отправлять клиенту «вложенные» ресурсы. Да и вообще, в современном софте REST до буквы соблюдать невозможно и необязательно, никто вообще не мешает создавать умные эндпоинты, которые отдают то что нужно. Заодно с контролем доступа и всяким прочим контролем

Кстати, в REST такое уже придумали, называется JSON-RPC, и спецификация есть :)

именно
и при этом, как и REST, он тебе не навязывает насколько «ортодоксально» делать. решай сам, по своей задаче.
надо, так можешь даже валидацию схемы json прикрутить, с контролем кастомных типов.
не надо — не прикручивай.

RPC действительно тоже работает с одним эндпоинтом, но сам RPC (не важно json, xml или чтото еще — это просто формат) возник задолго до реста и никакого отношения к нему не имеет

Насколько я помню, RPC это просто общий принцип, который может быть реализован с помощью разных протоколов. Одним из вариантов реализации как раз и является JSON-RPC. А то так можно сказать, что SOAP никакого отножения к RPC не имеет, потому что в его названии нет этих букв... или что например PostgreSQL не имеет никакого отнощения к SQL, потому что сам SQL возник задолго до PostgreSQL :)

Насколько я помню, RPC это просто общий принцип, который может быть реализован с помощью разных протоколов. Одним из вариантов реализации как раз и является JSON-RPC.

правильно, просто из

в REST такое уже придумали, называется JSON-RPC

может сложиться впечатление, что JSON-RPC, это какой-то частный случай REST или его подмножество или надстройка на ним, а это не совсем так,
GraphML, REST и (JSON)RPC — это в принципе 3 разных подхода к одной проблеме, каждый можно попытаться сравнить с каждым.

В данном случае идет обсуждение GraphML vs REST и JSON-RPC это просто пример того, что подобный принцип не является изобретением GraphML, а уже был реализован в REST

не является конечно, большинство нового — это какая-то улучшенная модификация того, что уже когда-то существовало в другом виде

А как у него с безопасностью? Кто занимается отсечением того, чего мне, как пользователю, получать нельзя?
Например где-то на N+1 уровне запроса я запросил поля, которые именно мне видеть нельзя... т.е. во внутренней логике они допустим участвуют, а вот на фронт отдавать нежелательно.

Кто занимается отсечением

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

N+1

рівня створити окрему структуру без того поля. Але це якийся дивне обмеження, можна приклад, коли таке потрібе?

Понятно, что бекенд, но если позволять только запросы с определенной структурой, то это уже есть, называется REST :)
На примере того же форума: есть два вида пользователей, у одного есть права администратора и он может смотреть и/или изменять email других пользователей в группе (но только в своей), у другого права посмотреть email нет, но при этом если один пользователь пишет ответ другому, то на email уходит оповещение.

Можна зробити так

type Account{
id: Int
email: String
profile: Profile
}

type Profile{
id: Int
name: String
}

type Post{
comment: [Comment]
}

type Comment{
id: Int
created_by: Profile
text: String
}

Лише адмін може робити запит на отримання списку Account з емейлами, посилати мутацію на зміну емейла, юзер може отимати лише коментарі, які посилаються на Profile.
Якщо юзер відповідає іншому юзеру, надсилає мутацію
mutation reply($comment_id: Int!, $text: String!), сервер надішле сповіщення на емейл того, хто створив комент з comment_id.

То есть получается, что ради одного поля нужно делить типы на два, писать для каждого резолверы, мутаторы и т.п... а если таких полей будет два, оба с разными правами? Ниже подсказали вариант со scope, но этого в стандарте вроде нет.
В как для меня, GraphQL имеет смысл только в случае открытого API, когда ты не знаешь, кто будет клиентом, если это продукт, для которого пишется как фронт так и бек одной командой, оно явно избыточно

Не избыточно. Даже в рамках одной команды, мы получаем агрегацию множественных запросов к REST в один запрос GraphQL (экономия на лэтенси). Мы уменьшаем bandwidth consumption, потому что отсылаем только то, что запрошено, а не все данные (в GQL запросить 3-4 уровня вложенности с 1-2 полями из каждого — это нормально. В РЕСТе пришлось бы как минимум получать все объекты полностью). Оба этих пунтка критичны для мобилок, но и для обычного современного богатого на функциональность фронта они улучшают UX

Даже в рамках одной команды, мы получаем агрегацию множественных запросов к REST в один запрос GraphQL (экономия на лэтенси).

это делается — элементарно и для REST

В РЕСТе пришлось бы как минимум получать все объекты полностью).

REST это всего лишь манифест, идея

Отсылать все, не уметь группировать запросы, и т.п. — это проблемы разработчиков

Оба этих пунтка критичны для мобилок

наймите нормальных бекендеров, а не джунов которые пытаются делать REST на основе простеньких примеров

«Недостатки REST» напоминают изъяны «водопода» у скрам коучей.

Вот что да, так это:

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

По другому
GraphQL решает не технические проблемы, а бюрократические, когда бекендеры и фронтендеры не могут пообщаться.

Как группировать запросы в REST, если каждый последующий зависит от ответа на предыдущий?

указав вид зависимости, или причину этой группировки.
а как же еще :)

тоже самое придется делать и на GraphQL

Ну вот давайте на примере. Давайте запросим имена супругов всех людей, отмеченных на фотографиях конкретного юзера. В GraphQL это делается одним запросом. Как этот единственный запрос выглядит в REST?

Как этот единственный запрос выглядит в REST?

как напишите, так и будет выглядеть.

Ваш вопрос звучит как:
Как будет выглядеть такой URL для GET?

Ответ: как сделаете, так и будет выглядеть

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

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

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

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

GraphQL именно так и подошли :)
Решая задачу для общего случая:
Мы понятия не имеем что захотят и как отображать на фронтенде

Я и прошу написать как будет выглядеть соответствующий урл. И заодно указать тип возвращаемого значения.

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

Каким боком GraphQL к персистентности? Это такой же слой, как и API Gateway. Если он у кого-то лезет напрямую в базу, то и в ресте этому помех нет — куча баз с рест API, в Spring даж есть Rest Repository

Мы понятия не имеем что захотят и как отображать на фронтенде

Именно. Потому что не должен бэкенд на каждый чих создавать endpoint, и в случае сложной системы данных, дает отличную гибкость и фронтам, и бекам

Я и прошу написать как будет выглядеть соответствующий урл.

откуда я знаю, как он будет выглядеть, если я не проектировал API для этой неизвестной системы?

но подход то упоминал уже

эндпойнт отвечает за ЧТО выдать
и имеет указание о фильтрации этой выдачи

А какой словарь у этого API для этого URL — зависит от остальных задач системы

Параметры эндпойнтов — это ж как DSL
какой нужен, такой и проектируется.
и, желательно, всегда — поменьше подробностей, как и положено DSL

В итоге
для одной системы это будет
/people?args=spouses,by_photo,owner=4234

Для другой
/people?filter=spouses&relation=photo,owner=4234&format=short

для третьей — по третьему

Или, самый универсальный способ
предоставить слать «SQL запросы» пусть там на фронте разбираются сами.

Фейсбук так и сделали. Потому что
— Очень большое количество независимых команд, как на беке так и на фронтенде
— Плюс есть неизвестные команды, которым нужно предоставить получение данных

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

дайте им «SQL» к вашим данным

/people?filter=spouses&relation=photo,owner=4234&format=short

Т.е. у вас тип возвращемого значения меняется в зависимости от набора полей? А как же один из принципов РЕСТа — ресурс идентификатор? Это уже не рест в примере, а просто http запрос) Который вы будет руками парсить и резолвить в коде. Поздавляю — вы придумали GraphQL over HTTP graphql.org/learn/serving-over-http

Т.е. у вас тип возвращемого значения меняется в зависимости от набора полей?

Зависит от того что вы называете — типом.

А как же один из принципов РЕСТа — ресурс идентификатор?

А он не нарушается. И, уже писал — REST это манифест.

ресурс идентификатор?

да, ресурс — люди
их идентфикатор — какие именно люди :)

«Ты скажи чё те надо, чё те надо. Может дам чё ты хошь»

А если ты не говоришь чего тебе надо, или «слов» таких тебе не дали, то да, API не позволяет получить чё ты хошь.

Это уже не рест в примере, а просто http запрос)

ок, покажите как выглядит REST без строки запроса :)

КОторый вы будет руками парсить в коде.

да, великое дело... еще с 90ых на это дело полно библиотек, но я почему-то буду парсить «руками»....

Поздавляю — вы придумали GraphQL over HTTP

Поздравляю, вы не знали что проблемы «реста» давно решены, и вдруг открыли для себя решение — других проблем :D
которое выглядит как стрельба из пушки по воробьям. потому что и предназначена эта пушка для решения задач компании размером с FB

Я и прошу написать как будет выглядеть соответствующий урл.

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

Потому что не должен бэкенд на каждый чих создавать endpoint

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

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

на другом ресурсе на днях поучаствовал и лайкнул мнение:

Alexey Slynko Я вижу для graphql только один плюс — когда есть некое публичное API для широкого круга пользователей, и отращивать ендпоинты на каждый чих пользователя никаких сил не хватит. Вот тут этот кастрированный SQL снимает головную боль в части поддержки, и перекладывает ее на бэкенд. В случае устовяшегося private API эта хрень только вредит. А, тут коллеги привели еще один довод — «типа, у нас же agile, требования меняются, удобнее дать фронтам graphql, пусть они сами данные комбинируют»
(конец цитаты)

«Кастрированный SQL» — вот что такое GraphQL

Добавлю еще плюс GraphQL
что он еще может понадобиться когда хранилища очень разные, и данные в разных местах, форматах, и получать их можно асинхронно, и тогда — все равно нужен слой который формирует запросы, сводит все в единые объекты — «эмулирует» подобие цельных и связанных доменных сущностей.
Ендпойнты могут выглядеть как угодно и в этом случае, но раз такой слой — дай по uuid ник с такого сервера, его фоточки с другого сервера, его коменты с третьего, а сообщения с чата с четвертого — то можно уже и фронтендерам дать возможность формировать запросы.

«Агрегатор данных» — вот второе название GraphQL

И так как он агрегатор и на изменения, то соответственно этот слой и знает — ник сохрани там, фоточки сям, и т.п.

И опять приходим к тому что
У вас такой проект? с таким зоопарком разномастных хранилищ данных, представление которых надо свести к нормализованному?

Тогда даже если снаружи у вас «простой REST», вам все равно понадобится писать такой агрегатор. Вот и не пишите, а берите для своего стека реализацию GraphQL

REST это всего лишь манифест, идея

Как же мало разработчиков это понимают

Я не можу уявити приклад для реального проекту, коли просто треба приховати одне поле для якогось типу юзера, зазвичай все набагато складніше, як у прикладі з форумом — адміну крім емейлів треба, наприклад, бачити додатково коли юзер був зареєстрований, остання активність, якісь інші речі, адмін може бачити призупинені аккаунти. При цьому для форуму потрібно по-іншому обробляти ті ж дані, наприклад, приховувати дані по призупиненим аккаунтам, та купа іншої логіки по отриманню даних, яка кардинально відрізняється від логіки в адмінці. І писати різні резолвери, схеми та мутації не просто є не надлишковим, а й покращує структуру коду, робить його простішим.

від логіки в адмінці.

а не треба адмінку робити SPA, коли можна її не робити як SPA

А зазвичай — можна. і мова не про PHP
Подивіться наприклад ту ж Джиру
от, клацнув на таскі на боарді, перщий же запит:
/rest/greenhopper/1.0/xboard/issue/details.json?rapidViewId=10&issueIdOrKey=FAN-1103&loadSubtasks=true&_=634532456745

Скільки адмінів планується, що такі витрати на них?

яка кардинально відрізняється від логіки

так. і тому — робіть для адмінський функцій інше API

Звісно, від проекту залежить, але зазвичай адміни можуть пожити й на менш зручному UI

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

тобто на це відповідали:

Например где-то на N+1 уровне запроса я запросил поля, которые именно мне видеть нельзя... т.е. во внутренней логике они допустим участвуют, а вот на фронт отдавать нежелательно.

мій підхід — пермішени повинні бути як найближче до тих данних, на які вони накладаються.

Якщо простенько:
Маємо 2 рівня:
F: рівень обробки данних на запит фронтенду
D: рівень отримання raw данних з систем персистентного збереження

F у нашому контексті то GraphQL
D то провайдери до «SQL», «Mongo», так кафок з касандрами

То більш надійно повідомити рівень D, хто зараз в них запитує данні, і щоб вони обробляли пермішени на них, та або — чистили те на що немає прав, або повертали помилку. І на зміни данних теж.
У суворих випадках кібербезпеки навіть самі БД мають спеціалізованих користувачів, під якими звертаються провайдери до них, і пееревіряють самі права на данні

Проектування прав, пермішенів, то окрема задача, і частенько не проста.
І в цьому проектуванні теж можна помилитися

А чим далі від данних — тим простіше помилитится, не помітити що на " где-то на N+1 уровне запроса я запросил поля".
тобто з рівня F.
А рівень D — дає все що запитають?
ну то вважайте що вас вже хакнули.

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

В том-то и дело, что client1 может получать это поле, а client2 — нет, роли у них разные, т.е. потенциально запрос может возвращать поля... но не для всех

да, но как я вижу, определение какую информацию отдавать какой роли — это дело секьюрити компонента, и не привязано к тому своду правил, на каком построена архитектура апи

Как бы да... но выше я уже описал пример, когда использовать поля внутри бекенда данной роли пользователя можно, а вот отдавать на фронт — нельзя. Т.е. для REST, как вариант, это будут два разных эндпойнта, соответственно возможность обращения к ним определяется ролью, т.е секьюрити компонент это просто набор соотвествий эндпойнт->роль. В случае GraphQL в этом секьюрити компоненте нужно проверять все поля, которые есть в запросе и, в случае появления нового поля расширять проверки.
Т.е. теоретически это все сделать можно, либо нужно кучу усилий потратить на безопасность, либо с тем же успехом можно SQL с фронта пробрасывать :)

В имплементациях GraphQL серверов это решается через скоупы. При описании модели ты сразу указываешь, какие поля доступны для какого скоупа на read, а какие — на write. Скоуп присваивается в момент авторизации, обычно в виде джота

Посмотрел, действительно через scope это можно решить... только, если я правильно понимаю, это кастомное расширение, т.е. не прописано спецификации

Конечно, это же Query Language. Все равно что от SQL требовать авторизации

А мне казалось, что CREATE ROLE, GRANT и т.п. уже в SQL-92 были :)

Да, но вы ж паритесь об этом на уровне query. Вы знаете, что если роли доступ запрещен, но то SELECT, UPDATE, INSERT не пройдет ) Плюс CREATE ROLE доступна ж не всем ) Никто не мешает в FGQL управлять ролями и скоупами через соответствующие мутации из-под админского скоупа, например )

Вполне возможно это сделать через директивы. www.apollographql.com/...​-tools/schema-directives

На уровне синтаксиса GraphQL это что-то типа аннотаций из Java/C#/Python, которые могут применяться как и к запросам/мутациям/подпискам, так и к отдельным полям. Описываете поле как «secretField: String! @checkIfAllowedToAccessSecretField» и потом в имплементации CheckIfAllowedToAccessSecretFieldDirectiveVisitor производите проверки. Директивы в спецификации есть.

Одно из решений N+1 описано в этом топике ниже в комментах

gRPC/что-угодно over Avro/Protobuf решает те же проблемы в рамках request-response (и не только) парадигмы намного проще, быстрее и удобнее.

плюс тысячи взрослых интеграций — хочешь в Кафку пиши, хочешь в файловую систему (parquet).

я наверное старый брюзжащий мудак, но этот ваш graphQL — это хипстерство.

rails, react, graphql — продолжите ряд.

Так про gRPC вже багато написано, що там ще писати?

В отличие от REST, необходим только один endpoint для работы.

Блин, чуваки изобрели query запросы :/

и сразу сделали на medium статью об этом.

Цікаво дізнатись як виглядає конкретний GraphQL HTTP запит. От коли виконується query операція

query GetAllFilms {
allFilms {
id
title
episode
}
}

як передається — у тілі POST запита, у посиланні GET запита ?
аналогічно з операцією Subscriptions — як вона реалізовує слухання ? це веб сокети ? чи як ?
Також цікаве порівняння GraphQL та OData ?

Мав справу з GraphQL та з OData протокололом (бекенд, python), мої враження:
OData протокол на порядки складніший, більше можливостей, наприклад, агрегації (OData Extension for Data Aggregation), дуже гнучка фільтрація/сортування даних (можна писати математичні вирази для фільтрування по полю, типу «$filter=Order/id mod 3 eq 2», «$filter= startswith(Customer/first_name, ’Alfr’)» ), атомарні батчі, де, якщо один підзапит на зміну/створення об’єкта фейлиться, то всі інші зміни мають відкотитися, та багато чого іншого.
Але при цьому у всіх опенсорсних рішеннях сервера імплементовано лише якусь малу частину можливостей, описаних в протоколі, а на пітоні майже нічого немає.

GraphQL досить простий, документацію можна прочитати за пару годин, можливості описані в статті, і їх і приблизно не так багато, як в OData, хоча й я не можу уявити в якому випадку може знадобитися гнучкість OData.
Для GraphQL є купа класних готових рішень.

Запити читати набагато простіше читати в GraphQL, тіло запиту:
```
{
«query»: «query{
customer{
first_name
order{
count
product{
name
price
}
}
}

}
```
в одаті get запит пишеться в урлі:
https://example.com/customer?$select=first_name&$expand=order($select=count;$expand=product($select=name,price))
коли в запитах багато рівнів вкладеності, то розбирати запит одати — це біль.
Відповідь сереверу для прикладів вище буде приблизно однаковою по структурі.
p.s. як зробити нормальне форматування коммента, щоб відступи на початку рядка не зникали?

В статье есть общие черты, есть преимущества GraphQL над REST.
Какие недостатки GraphQL по сравнению с REST?

отмечу главный недостаток: отсутствие версионирования. Как итог, в коде живут скелеты ископаемых, от которых можно избавиться, лишь только сделав force update моб. апликухи

Разве в REST не нужно так-же поддерживать v1 до тех пор пока не потухнет последний телефон с аппой стучащейся на v1?

Насколько я понимаю — можно сделать принудительный update. Скажем просто не давать приложение работать, если нет update. На iphone такое постоянно — особенно с банковскими приложениями

не только на айфоне. практически все мобильные клиенты банков

Простите, откуда у Вас информация о том, что REST не поддерживает версионирование ?
этот вопрос давно закрыт, есть штатные механизмы и инструменты, есть несколько подходов, к примеру как через URL или через заголовки и так далее.

Я наверное неправильно выразился — в

GraphQL

нет версионирования, и это его главный недостаток перед REST ибо есть мутация addEntity, есть запрос getEntity и любые изменения в них приведут к краху на фронте, в REST мы можем добавить к урлу, к примеру /v1 и таким образом легко решаем проблему

Если сделать вторую пару назвав их addEntityV1 и getEntityV1? Или любое другое имя)

Хотелось бы уточнить более конкретно о каких изменениях в get*/add* паре вы говорите?

В GraphQL Есть 2 типа операций мутации ( create / update / delete ) и запросы на чтение.
К примеру у нас есть мутация

mutation editEntity($id: ID!, $type:string, $text: String) {
  editEntity(input: {id:$id, type: $type, text: $text}) {
    entity {
      id,
      text
    }
  }

В таком форме вы пошлете запрос на сервер, разве что еще добавите query variables.

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

mutation editEntity($id: ID!, $type:string, $text: String, $text2: String) {
  editEntity(input: {id:$id, type: $type, text: $text, text2: $text2}) {
    entity {
      id,
      text,
      text2
    }
  }

В итоге, как вы правильно подметили, у нас на бек енде, для разруливания ситуации, уже 2 обработчика — для мутации editEntity и editEntityV1. И если не использовать принудительное обновление апликухи, то бек енд будет саппортить и старую версию мутации, и новую. В итоге скапливается много мусора :(

А в REST не нужно саппортить старую мутацию?
Немного не улавливаю почему на беке не будет точно также два обработчика для двух разных версий реста...

Ну официальная идея, что мутация на клиенте остается такой же.

Просто на сервере проверяется наличие text2 — если есть — одно поведение, если нет — другое.
Неприятно, но что поделать.

и это его главный недостаток перед REST

в соот. со стандартом мы ведь четко передаем набор атрибутов для данных, соот. можно решить, пусть хоть +100 атрибутов в плюс, все равно, а чтобы явно указать что поля больше не стоит использовать — есть механизм не рекомендованных полей Все решаемое.

Я же лично вижу главный недостаток GraphQL в производительности.
Начиная от вопросов кеширования с использования наработок на уровне протокола HTTP заканчивая гарантированно выстреливающими другими вопросами ( к примеру — Н+1 запросов )

гарантированно выстреливающими другими вопросами ( к примеру — Н+1 запросов )

Ну, на этот счет можно использовать github.com/graphql/dataloader

это гитхаб лежит :)

Та просто гитхаб лежит

Да, одна из реализаций шаблона GraphQL Dataloader для JS ,
вот подробное описание самого шаблона, кому интересно:
medium.com/...​n-visualized-3064a00f319f

При ряде случаев вреда больше, чем пользы.

Официальное мнение:

Versioning

While there’s nothing that prevents a GraphQL service from being versioned just like any other REST API, GraphQL takes a strong opinion on avoiding versioning by providing the tools for the continuous evolution of a GraphQL schema.

Why do most APIs version? When there’s limited control over the data that’s returned from an API endpoint, any change can be considered a breaking change, and breaking changes require a new version. If adding new features to an API requires a new version, then a tradeoff emerges between releasing often and having many incremental versions versus the understandability and maintainability of the API.

In contrast, GraphQL only returns the data that’s explicitly requested, so new capabilities can be added via new types and new fields on those types without creating a breaking change. This has led to a common practice of always avoiding breaking changes and serving a versionless API.

1) GraphQL требует больше кода на server-side.

В случае с REST — просто end point описал и все. Ну и еще модель.
В случае с GraphQL — нужно описать схему, типы, запросы/мутации, имплементацию запросов (resolver)
Чисто субъективно больше возни.

2) И есть еще нюансы с клиентом. Клиент — достаточно сложный и может кешировать запросы.
Если кеширование включено, то нужно следить, что бы после выполнения мутаций, обновлялись (refetch) затронутые запросы

Затем, если есть некоторая одна и та же сущность (условно Car с id=5) которую вытягивают два разных запроса с разными наборами полей, то при включенном кешировании, результатом обоих запросов будет результат последнего реально выполненного к серверу запроса.
Т.е. клиентский код может получить больше полей чем ожидает. Или даже меньше.

По крайней мере так работает Apollo Client (JS)

---

T.e. если проект — простое CRUD приложение с одной базой, то имхо овчинка не стоит выделки.

Щодо першого пункту — хіба це не залежить від мови та доступних фреймворків? Якщо брати django, то я б сказав, що на drf (rest) та django-graphene писати коду приблизно однаково,

схему, типы

django-graphene будує з моделі бд.

Кешування складно імплементувати.

Какие недостатки GraphQL по сравнению с REST?

— Схема БД прибивается гвоздями к фронтенду. Впрочем в тупой реализации REST API тоже
— Сложно организовать проверку прав
— Сложно подцепить агрегационные поля
— Сложно превращать одиночные запросы к БД в bulk. Проблема правда любого серьезного ORMа. Но в ORMе проще, только что одной строчкой исправил — было:
2 789 queries found. Total processing time: 1,943 ms;
Потому что:
SELECT * FROM prp_people WHERE id=...
с разных мест
Закешировал, на один дерг эндпойнта, стало:
567 queries found. Total processing time: 388 ms;
А в резолверах GraphQL так просто не получится
— Сложно проверять ораничения на объемы запрашиваемых данных, «паджинации» фактически нет, неправильный запрос снаружи может начать вытягивать всю БД

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

и

GraphQL — это концепция создания API, которая обеспечивает слабую связность клиента и сервера

а вот как раз нет.
Вернее — чтобы не прибивать гвоздями к персистентой схеме — придется потрудится много больше, чем понавесить к REST эндпойнтам специализированные
fields=, filter=, include=, exclude=, format=, extented=, context= и т.п.

Боюсь, при чтении документации по GrpahQL вы упустили разделы про директивы, даталоадеры и паджинацию. У GrpahQL есть свои недостатки, но далеко не те, что вы здесь упомянули.

вы упустили разделы про директивы, даталоадеры и паджинацию.

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

У GrpahQL есть свои недостатки, но далеко не те, что вы здесь упомянули.

ну так расскажите о них, более важных недостатках, а не «я доку прочел, а вы нет!» :)
особенно ценен будет ваш опыт использования, где вы столкнулись с этими недостатками.
я попробовал, покрутил, и пришел к выводам, для себя, которые озвучил немножко в коментах

давайте свои выводы.

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

Підписатись на коментарі