Определение мутаций в graphql через поля: это плохая практика?

Предположим, у вас есть тип user, а у user много posts. Затем представьте, что вы хотите найти пользователя и удалить все его сообщения. Один из способов сделать это - реализовать следующее поле mutation:

field deleteAllPosts, types[Types::PostType] do
  argument :user_id, types.String

  resolve -> (obj,args,ctx){ 
    posts = Posts.where(user_id:args[:user_id])
    posts.each{|post| post.destroy}
  }
end

Тогда запрос

mutation {
  deleteAllPosts(user_id:1)
}

удалит все сообщения пользователя с идентификатором 1.

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

Идея состоит в том, чтобы вместо этого поместить поле deletePost для PostType и поле findUser на мутацию (обычно это поле запроса). Предполагая, что очевидно, как эти поля будут определены, я бы сделал запрос

mutation{
  findUser(id:1){
    posts{
      deletePost{
      id
      }
    }
  }
}

Это плохая идея?

Редактировать в ответ на отзыв: Меня беспокоит возможность того, что пользователь мог, в принципе, сделает выбор deletePost внутри запроса. Но мне хочется сказать, что это «их вина». Я бы сказал нравиться, что «этот выбор может быть сделан только в том случае, если он находится внутри запроса на мутацию», но я не думаю, что это возможно в GraphQL.

Чтобы избежать проблемы XY, вот почему я стремлюсь использовать эту идею, а не исходную. Он кажется более выразительным (иначе говоря, менее избыточным). Предположим, что через некоторое время вы решили, что хотите удалить все posts для тех users, принадлежащих конкретному group. Затем в том, что я считаю «соглашением», вы должны создать совершенно новое поле мутации:

field deleteAllPostsInGroup, types[Types::PostType] do
  argument :group_id, types.String

  resolve -> (obj,args,ctx){ 
    posts = Group.find_by(args[:group_id]).users.map{|u| u.posts}.flatten
    posts.each{|post| post.destroy}
  }
end

тогда как в моем предложенном соглашении вы просто определяете тривиальное поле findGroup (но вы должны определять его при мутации, где оно не принадлежит) и выполняете запрос:

mutation{
  findGroup(id:1){
    users{
      posts{
        deletePost{
        id
        }
      }
    }
  }
}

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

Второе редактирование: Кажется, есть четко определенный компонент этого вопроса, который я задал здесь. Может оказаться, что один из этих вопросов отвечает на другой, и его можно закрыть, но я пока не знаю, как это сделать.

2
0
254
1

Ответы 1

По сути, это проблема качества кода, которая похожа на вопрос о принципе DRY или инкапсуляции.

Цитата из https://graphql.org/learn/queries/ гласит:

In REST, any request might end up causing some side-effects on the server, but by convention it's suggested that one doesn't use GET requests to modify data. GraphQL is similar - technically any query could be implemented to cause a data write. However, it's useful to establish a convention that any operations that cause writes should be sent explicitly via a mutation.

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

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

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

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

Короче говоря, все дело в том, чтобы облегчить себе и другим жизнь в будущем.


РЕДАКТИРОВАТЬ

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

Если это не так, то что, если вы хотите удалить, скажем, 5 определенных пользовательских сообщений? Не могли бы вы дать дополнительные параметры findGroup, а затем передать их по дереву? Но тогда почему метод findGroup должен знать, что вы будете делать с его результатами? Это противоречит самой идее гибкого запроса. Что, если бы вы также захотели произвести мутации для пользователей? Еще параметры для findGroup? Что, если пользователи и сообщения можно запрашивать по-другому, например, пользователей по доменам, сообщения по категориям и т. д.? Определите и там такие же параметры? Как вы можете гарантировать, что при каждой операции (особенно если вы выполняете несколько из них одновременно) все реляционные ссылки должным образом стираются в вашей базе данных? Вы должны представить себе все возможные комбинации запросов и мутаций запросов и код, соответствующий им. Поскольку размер запроса не ограничен, это может оказаться очень сложным. И даже если цель отдельного запроса-мутации (deletePost) ясна и понятна, общий запрос не будет. Вскоре ваши запросы станут слишком сложными для понимания даже для вас, и вы, вероятно, начнете разбивать их на более мелкие, которые будут производить только определенные мутации. Таким образом вы вернетесь к исходному соглашению, но к его более сложной версии. Вы, вероятно, также в конечном итоге определите некоторые регулярные мутации. Как бы вы обновили или добавили сообщения, например? Это распространило бы вашу логику повсюду.

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

Это все проблемы потенциал в будущем (и, вероятно, их будет больше). Если вас это не касается, продолжайте реализацию. Я лично сбежал бы из проекта, который сделал это, но если вы действительно умны, я не вижу ничего, что технически полностью помешало бы вам достичь того, чего вы хотите:]

Спасибо за Ваш ответ. Я ценю ваши общие моменты, но я не думаю, что они применимы к этому примеру (неясно, делаете вы это или нет; пожалуйста, скажите). Мутация deletePost, которую я определил как является, читаемая / маленькая / хорошо названная, на мой взгляд. Я добавил некоторые детали в ОП, чтобы объяснить.

preferred_anon 14.09.2018 10:32

Вы случайно не используете MongoDB или что-то подобное NoSQL'ы?

Avius 14.09.2018 10:45

Нет, я не знаком ни с одним из них.

preferred_anon 14.09.2018 10:47

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

Avius 14.09.2018 11:41

Спасибо! 100% согласны с тем, что это с нетерпением (в каком-то смысле все "лучшие практики" таковы). Если вы хотите удалить 5 конкретных постов (допустим, вы знаете их идентификаторы), то это именно то, что вам нужно, чтобы написать поле deletePostByID для мутации обычным способом. Вы можете использовать findGroup только в том случае, если описание «сообщений, которые вы хотите удалить» каким-то образом семантически связано с группами. Выборы, такие как group{users{posts}}, будут определены для этих типов уже, потому что это семантические вещи, которые вы, возможно, захотите запросить.

preferred_anon 14.09.2018 12:02
findGroup не будет делать ничего, кроме выборки объекта group по его идентификатору. Он будет идентичен стандартному методу запроса, который вы напишете, с той лишь разницей, что вы используете его для мутаций (поскольку это поле мутации), а не для запросов просто.
preferred_anon 14.09.2018 12:05

Да, я понял это. Я говорил, что ЕСЛИ вы хотите параметризовать удаление, технически параметры должны быть сначала переданы в корневой запрос, что не является интуитивно понятным IMO. Похоже, вы намереваетесь только определить простые модификации без параметров в запросе, поэтому для этой цели ваша идея должна сработать. Я бы все равно не пошел по этому пути из-за организационных аспектов, но вы можете возразить, что это несколько самоуверенно, и я думаю, что вполне может быть. В качестве последнего совета вы можете исследовать выполнение мутаций в операциях GET, чтобы получить более подробное представление. Удачи!

Avius 14.09.2018 12:32

Другие вопросы по теме