LINUX.ORG.RU

Формирование ответа на мутацию в graphql

 ,


0

2

Подскажите, как правильно задать ответ на мутацию в gql? Суть - есть некий объект User

type User {
  address: Address
  status: Status
  inventory: Inventory 
}

Все поля - комплексные типы, например в каждом есть поля x и y. Вложенность может быть уровней на 10-15, полей в жизни тоже не 3, а 33.

Есть мутация, которая изменяет например поля x и y из Address и поле x.x из Status.

type Mutation {
  updateAddress(...): ????
}

Как правильно написать тип ответа чтоб для фронтенда было очевидно что именно поменялось в объекте?

В плане я понимаю что можно просто вернуть объект и фронт может сам запросить что угодно, но запрашивать сразу 100500 полей чтоб понять что там поменялось им самим нафиг не надо. Хз, например указывать типом ответа интерфейс с нужным набором полей и потом указывать что User его имплементит? Или как-то вручную формировать ответ, но при этом на клиенте может влет сломаться кеш из-за несоответствия типов.

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

В чатгпт пока не ходил, но в доке и best practices явного ответа не нашёл

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

Пока самым логически корректным выглядит вариант с интерфейсами типа

Mutation updateAddress(...): UserWithAddressAndStatus

type User implements UserWithAddressAndStatus {
  ...
}

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

★★★★★

Последнее исправление: upcFrost (всего исправлений: 2)
Ответ на: комментарий от rtxtxtrx

Какую готовую либу, ты о чем? Вопрос про проектирование API, а не про то какую либу взять.

Ну и не про то что кто-то «всю базу достанет», а про то как корректно сказать этому кому-то какие поля меняются после мутации.

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost
type Mutation {
  updateUser(id: Int!, data: UserData!): ...
}

input UserData {
  name: String
  email: String
  ...
}

А запрос типа такого:

mutation {
  updateUser(
    id: 123
    data: { 
      email: "govno@mail.sru"
  })
}

Странный вопрос…

А тебя же ответ интересует. Просто поля User… При регистрации так же возвращается User. Создание/обновление -> Возвращаем объект, при удалении можно что-то типа {"deleted": true}. Как в Rest’е, короче

rtxtxtrx ★★
()
Последнее исправление: rtxtxtrx (всего исправлений: 3)
Ответ на: комментарий от rtxtxtrx

Как в Rest’е, короче

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

При регистрации так же возвращается User. Создание/обновление -> Возвращаем объект,

Например у объекта User 60 полей, каждое глубиной 10. Для gql это абсолютно нормально. Пытаться достать все поля объекта на каждую мутацию чтоб локально понять что поменялось фронт не будет, это жесть, особенно для мобилок на дохлом жопорезе в условной Альберте

Суть что мутация updateUserPic должна однозначно говорить фронту (прямо в схеме) что в ней изменились вот эти поля, а на другие можно забить. Видимо это правда проще всего интерфейсом делать, то есть мутация возвращает интерфейс UserWithPic, а User этот интерфейс имплементит, тогда сразу понятно что и куда

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

Нет. Делаешь универсальный метод и все

Например у объекта User 60 полей, каждое глубиной 10

Так можно указать возвращаемые поля, например, только id… Что-то возвращать всегда нужно, так как пользовательские данные тот же email НОРМАЛИЗУЮТСЯ (например, точка в конце доменного имени опциональна и убирается)… А в прочем тебе никто не запрещает делать как ты хочешь, я лишь написал как бы сделал я, а я человек ленивый и в рот сношал делать десятки методов для обновления каждого поля вместо одного универсального

++ никогда не знаешь, что на фронте понадобится, а потому проще все возвращать

rtxtxtrx ★★
()
Последнее исправление: rtxtxtrx (всего исправлений: 3)

Как правильно написать тип ответа чтоб для фронтенда было очевидно что именно поменялось в объекте?

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

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

ya-betmen ★★★★★
()
Ответ на: комментарий от rtxtxtrx

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

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

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

А, ну и видимо проектирование api и separation of concerns тоже не в кассу немного

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

Ну тут ясно что ты начитался умных книг от непризнанных гениев и теперь какую-то Clean Architecture пытаешься реализовать вместо того чтобы сделать как все…

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

rtxtxtrx ★★
()
Ответ на: комментарий от ya-betmen

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

Объем данных, быстродействие, плюс магия с кешами на самом деле не так круто работает как рассказывает документация.

Ну, смотри, есть схема с 3-4 топ-типами с нехилой глубиной и шириной уже на первом уровне. Есть компоненты того же реакта, подвешенные на фрагменты. Есть query, набиваемый этими фрагментами, он выполняется при загрузке. Есть мутации, которые этот стейт немного меняют. Например как в доке закидывают новый ToDo в список и снимают баланс.

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от rtxtxtrx

вместо того чтобы сделать как все

Знаешь, я как бы достаточно видел имплементаций а-ля GovnoQL (и пару раз писал их сам, к сожалению), и большинство людей по опыту реально юзают gql как rest даже не подумав включить голову и например понять что nullability это не только про null, но и про error propagation boundary, или что поля в query и поля в типах это одно и то же и они имеют одинаковые возможности и этим внезапно можно пользоваться. Да или хоть самое банальное - как работают резолверы, это прямо в спеке написано, но люди упорно думают что это просто typing-обертка

Писать gql схему как rest это все равно что в монге пытаться каждый тип свалить в отдельную коллекцию как в sql, в итоге будет шлак, тормоза и race condition на каждой операции

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от rtxtxtrx

Вот прям в твоём примере открой модель user и посмотри какие там поля. Например есть currentMembership. Исходя из этой схемы тебе понятно повлияет ли данный запрос на это поле, или ты собираешься его на каждый чих выкачивать заново?

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от rtxtxtrx

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

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

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

Погодь, а нафига всё выбирать если у тебяидет сохранение определённого объекта. Он же должен либо как ответ либо как апдейт прилететь.

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

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

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

Погодь, а нафига всё выбирать если у тебяидет сохранение определённого объекта. Он же должен либо как ответ либо как апдейт прилететь.

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

Пример:

interface Completable {
  id: ID!
  done: Boolean!
}

type ToDo implements Completable {
  id: ID!
  done: Boolean!
  pics: ...
  text: ...
  ... # пачка полей
}

type Mutation {
  markToDoAsDone(id: ID!): Completable
}

Из такой модели сходу понятно что мутация меняет флаг done и другие поля можно не тянуть. Если модель крупная - это может здорово сэкономить трафик и упростить взаимодействие с фронтом

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

Фронт может и поймёт, но там же куча ручной работы будет.

Ну смотри, ты можешь сделать тип Апдейт, у которого внутри будет лежать оновленный объект с одним обновленным полем. Или в пару к каждому типу завести ТипАпдейт с копиями полей. Или в каждую сущность добавить булевое поле апдейт, которое не хранилось бы в бд.

Я с графкуэл дел особо не имел поэтому рассуждение из общих соображений.

ya-betmen ★★★★★
()
Ответ на: комментарий от upcFrost

фронт из схемы понимал что меняется именно этот флаг, а не картинки, статус аккаунта или ещё что-то.

если фронт не понимает что и в каком месте у него поменялось, то проблема тут не в gql

а если понимает, то можно и partial json update

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

olelookoe ★★★
()
Ответ на: комментарий от ya-betmen

Или в пару к каждому типу завести ТипАпдейт с копиями полей

Кеш сломается

Ну смотри, ты можешь сделать тип Апдейт, у которого внутри будет лежать оновленный объект с одним обновленным полем

Аналогично

Или в каждую сущность добавить булевое поле апдейт, которое не хранилось бы в бд.

Аналогично

Я думаю пойти в дискорде у авторов спросить что они думают.

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от olelookoe

если фронт не понимает что и в каком месте у него поменялось, то проблема тут не в gql

Человеческий фактор желательно минимизировать где возможно. А то можно ж вообще без схемы если все всё «понимают»

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

Сорян, мы на бэке слишком подсели на separation of concern через резолверы и валидацию директивами, теперь чистый рест кажется какахой

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от ya-betmen

Фронта. Придётся писать тонну обвязок. Кеш завязан на тип объекта, это можно переопределить но немного больно, он и так не особо хорошо работает

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

Человеческий фактор желательно минимизировать где возможно.

там было не про человека, а про реализацию

мы на бэке слишком подсели на separation of concern через резолверы и валидацию директивами

кому и кобыла невеста (с)

gql это про ленивое проектирование, болт на контроль доступа, нагрузки на сервер и кэширование.

остальное - теорема эскобара.жпг

olelookoe ★★★
()
Ответ на: комментарий от upcFrost

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

А так тебе нужно просто чтобы чать кеша хранила данные 0 сек и где-то жил листенер который бы перекладывал поля.

Короче у меня больше идей нет.

ya-betmen ★★★★★
()
Ответ на: комментарий от olelookoe

там было не про человека, а про реализацию

Про схему апишки, а не реализацию

gql это про ленивое проектирование, болт на контроль доступа, нагрузки на сервер и кэширование.

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

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

Фронт и так будет переделываться с нуля. Сейчас этап проектирования.

где-то жил листенер который бы перекладывал поля.

Можно subscription на поле. Но одна подписка это один вебсокет. И тут либо разнести компоненты по разным подпискам и количество ws пробьёт потолок, либо сжимать в одну и тогда та же проблема только хуже (на каждый чих идёт полметра данных)

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

Кажется ты отталкиваешься от каких-то ограничений серверных/клиентских либ, которые мне неизвестны.

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

Вот например.

Можно subscription на поле. Но одна подписка это один вебсокет.

Вот эту фразу я вообще не понимаю. Зачем подписки на поля? Почему ты не можешь использовать столько вебсокетов сколько тебе нужно?

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 2)
Ответ на: комментарий от olelookoe

В решение у проблемы простое: выгнать сепаратнутых концернов, которые будут полгода что-то проектировать, а в итоге выйдет говно, потому как всякие мартины, они код не пишут, они пишут книги, и их примеры дальше хеллоувордов не работают 🤣

rtxtxtrx ★★
()
Ответ на: комментарий от upcFrost

Не совсем так

совсем.

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

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

и, наконец, новенькая блестящая MVP-шечка выкатывается в мир, где в 99% остается невостребованной и дохнет

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

подход норм, стартап отливать в граните незачем.

olelookoe ★★★
()

так, ладно, поругать поругал, но надо ж и конструктива влить в беседу

смари

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

json patch решает твой вопрос
а отправляешь ты его по sse

бэк ты дергаешь за какое хочешь вымя, хоть за rest, хоть за gql - по вкусу.
дернул, прицепил id
по sse получил id + json patch

прилетел патч на твои развесисЬтые данные
фронт применил
ну и потом красиво отобразил, вестимо.

успехов

olelookoe ★★★
()
Ответ на: комментарий от olelookoe

gql это про ленивое проектирование

лолшто?!

болт на контроль доступа

лолшто?!

нагрузки на сервер и кэширование

лолшто?! бекенд - это прослойка между базой и клиентом… в плане нагрузки на базу, выборка данных через 1000 параллельных запросов намного тяжелее чем вытянуть все данные через graph пусть там под капотом и сотни запросов, но не 10000 как при параллельных… какие-то странные рассуждения

json patch

вариация на тему xmlpath. он ему в этом конкретном случае как собаке пятая ноге

rtxtxtrx ★★
()
Ответ на: комментарий от rtxtxtrx

gql это про ленивое проектирование

лолшто?!

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

нет никакой технической возможности автоматом транслировать 1:1 все gql в, скажем, sql запросы, не говоря уже rest и других источниках данных.

это значит, что есть всего два варианта:

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

второе проще и дешевле

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

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

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

olelookoe ★★★
()

Ты поешь говна, к сожалению.

Если у тебя вложенность на 10 уровней каждая шириной в десятки полей, ты уже не смог ни в какой separation of concerns, на который ты так часто ссылаешься.

Если ты проектируешь систему от данных (условно, бэк), сперва наведи в них порядок. Потом, скорее всего, окажется, что rest или (g)rpc будет достаточно. И эти данные в будущем будет проще продать отличным от веб браузера клиентам и 3rd party интеграторам. KISS!

GraphQL, как уже отметили выше, выходит на сцену, когда ты проектируешь систему от ее ui/ux (фронт). Это само по себе не лучшее архитектурное решение, поэтому, если ты уже в данном положении, то проблема дублирования данных — это одно из последних, о чем тебе вообще стоит переживать. Всасывай все это говно во фронт, и пусть разруливает это react/redux — говнотехнологии, решающие говнопроблемы.

anonymous
()
Ответ на: комментарий от vazgen05

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

Почему не использую? Он как раз идёт в кеш

Можешь еще сам в кэш apollo записать изменения

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Если у тебя вложенность на 10 уровней каждая шириной в десятки полей, ты уже не смог ни в какой separation of concerns, на который ты так часто ссылаешься.

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

GraphQL, как уже отметили выше, выходит на сцену, когда ты проектируешь систему от ее ui/ux

На самом деле оно у меня так и есть. Пока я тут спрашиваю со стороны бэка, фронты делают то же самое со своей стороны, и мы все это делаем по ui/ux-макету. Завтра делимся результатами и смотрим что вышло.

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от olelookoe

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

Вот это зря. У нас есть куски с таким подходом, и есть куски где люди пару дней подумали мозгом прежде чем браться за клавиатуру. И где-то половина тех кусков где включали мозг получилась реально удачной (в плане что спасала нас от тотального перепиливания всего сильно больше одного раза). А вот где хреначили по ходу дела - почти везде в какой-то момент наставал «час расплаты» когда приходилось тратить недели чтоб заново включить мозг и всё переписать.

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

кто именно там выбирает начинку эвента

ты сам

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

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

Как правильно написать тип ответа чтоб для фронтенда было очевидно что именно поменялось в объекте?

json patch - это и есть тот самый дифф
в нем очевидным образом перечисляются все изменения

olelookoe ★★★
()
Ответ на: комментарий от olelookoe

Ну, есть вот такая штука, в целом похоже

https://github.com/n1ru4l/graphql-live-query

Фронт правда частично против partial results, что вообще хз т.к. это сама суть и если без них то проще rest

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

Фронт правда частично против partial results

креативно там у вас

видимо кто-то должен написать фронтерам либу, засасывающую дифф в какой-нибудь там redux, а дальше они пусть сами сношаются как хотят

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

olelookoe ★★★
()
Ответ на: комментарий от olelookoe

креативно там у вас

Да стандартно. Фронт хочет чтоб бэк отдавал 1:1 то что они будут отображать без какой-либо логики, бэк хочет на своей стороне тоже не держать тонны конвертеров и логики. Обычная тема

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

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

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 2)
Ответ на: комментарий от ya-betmen

практика говорит, хочешь быстро и экономно - пили своё.

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

upcFrost ★★★★★
() автор топика