Рубрики
Без рубрики

Организация мутаций GraphQL

Уборка грубра. Обновление (5/7/2018): anders ringqvist (комментарии) заметил отчет о выпуске, который может вызвать проблемы при использовании этого подхода. Пожалуйста, смотрите мой следующий пост. – Великое деление в схемы GraphQL проходит между запросами и мутациями. Способ запроса читает данные из источника данных, таких как

Автор оригинала: FreeCodeCamp Community Member.

Уборка грубра.

Обновление (5/7/2018): Anders ringqvist (комментарии) заметил Отчет о выпуске что может вызвать проблемы При использовании этого подхода. Пожалуйста, смотрите Мой следующий пост Отказ

Великое разрыв в схемы Graphql работает между Запросы и мутации Отказ Способ запроса считывает данные из данных DataSource, таких как база данных SQL или файловой системы или даже удаленный сервис. Принимая во внимание, что запросы могут быть выполнены одновременно, мутации не могут.

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

Запросы – это r ‘в Crud (создайте, чтение, обновление и удаление). Код в этой статье отключается от Пример запуска Pailspad Отказ В коде LaunchPad есть определенный запрос, который вернет авторские сообщения, учитывая идентификатор автора. Однажды я продлил этот пример на мой пост о Тестирование интерфейсов GraphQL Отказ В этом посте я добавил книги в микс, и здесь я продю эту идею.

Авторские сообщения

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

Mutation: {
    upvotePost: (_, { postId }) => {
      const post = find(posts, { id: postId });
      if (!post) {
        throw new Error(`Couldn't find post with id ${postId}`);
      }
      post.votes += 1;
      return post;
    },
  },

Для реализации голосования я просто создаю подобное DownVotePost Мутация:

Mutation: {
...

  downvotePost: (_, { postId }) => {
      const post = find(posts, { id: postId });
      if (!post) {
        throw new Error(`Couldn't find post with id ${postId}`);
      }
      post.votes -= 1;
      return post;
    },
  },

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

Кроме того, я хотел бы избавиться от Употепост и DownVotePost Называть и вместо этого полагаться на контекст, как Post.upvote () и Post.downvote () Отказ Это можно сделать, имея метод мутации, вернуть набор операций, которые влияют на данный пост.

Постопс это тип, определенный как:

type PostOps {
          upvote(postId: Int!): Post
          downvote(postId: Int!): Post
      }

Существительное Пост был исключен из названия Verb-существительного способа, как это избыточно. Код Resolver работает в пост-контексте, через Постопс :

const voteHandler = (postId, updown) => {
    return new Promise((resolve, reject) => {
        const post = posts.find(p => p.id === postId);
        if (!post) {
            reject(`Couldn't find post with id ${postId}`);
        }
        post.votes += updown;
        resolve(post);
    })
};

const PostOps =
    ({
        upvote: ({
            postId
        }) => voteHandler(postId, 1),
        downvote: ({
            postId
        }) => voteHandler(postId, -1)
    });

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

Теперь вместо того, чтобы вызывать метод мутации непосредственно на корневом уровне, он называется в контексте Пост :

mutation upvote {
  Post {
    upvote(postId: 3) {
      votes
    }
  }
}

И это возвращение:

{
  "data": {
    "Post": {
      "upvote": {
        "votes": 2
      }
    }
  }
}

Все идет нормально. Методы могут быть высушены дальше, перемещая Сообщение ID аргумент на верхний уровень:

extend type Mutation {
        Post
(postId: Int!): PostOps
}

type PostOps {
          upvote: Post
          downvote: Post
      }

Почтовое Резольверы останутся без изменений: они все еще берут постид Параметр, но этот параметр передан из Пост к Постопс Отказ Следующий пример объяснит, как это работает подробно.

Авторы и книги

Авторы в моем приложении не только авторские должности, но некоторые имеют авторские книги. Я хочу выполнить классические работы, обновлять и удалять операции в списке книг, авторуемых. Автором тогда:

input AddBookInput {
            ISBN: String!
            title: String!
        }
            
input RemoveBookInput {
            bookId: Int!
        }
            
input UpdateBookInput {
          ISBN: String!
          title: String!
      }
          
type AuthorOps {
          addBook(input: AddBookInput!): Int
          removeBook(input: RemoveBookInput! ): Boolean
          updateBook(input: UpdateBookInput!): Book
      }

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

В этом случае ISBN – не сгенерированный идентификатор книги, поэтому включен в CreatebookInput Отказ Книги также имеют автор. Откуда это придет? Оказывается, авторид передается на Доклад Resolver из контекста, из которого называется создание операции, а именно Автором :

extend type Mutation {
        Post: PostOps
        Author(id: Int!): AuthorOps
      }

Resolver для Автором выглядит как:

const addBook = (book, authorId) => {
    console.log("addBook", book, authorId)
    return new Promise((resolve, reject) => {
        book.authorId = authorId
        books.push(book)
        resolve(books.length)
    })
}

const removeBook = (book, authorId) => {
    return new Promise((resolve, reject) => {
        books = books.filter(b => b.ISBN !== book.ISBN && b.authorId === authorId);
        resolve(books.length)
    })
}

const updateBook = (book, authorId) => {
    return new Promise((resolve, reject) => {
        let old = books.find(b => b.ISBN === book.ISBN && b.authorId === authorId);
        if (!old) {
            reject(`Book with ISBN = ${book.ISBN} not found`)
            return
        }
        resolve(Object.assign(old, book))
    })
}

const AuthorOps = (authorId) => ({
    addBook: ({
        input
    }) => addBook(input, authorId),
    removeBook: ({
        input
    }) => removeBook(input, authorId),
    updateBook: ({
        input
    }) => updateBook(input, authorId)
})

Теперь давайте создадим книгу и обновите ее:

mutation addAndUpdateBook {
  Author(id: 4) {
    
addBook(input: {ISBN: "922-12312455", title: "Flimwitz the Magnificent"})
  }
  Author(id: 4) {
    
updateBook(input: {ISBN: "922-12312455", title: "Flumwitz the Magnificent"}) {
      authorId
      title
    }
  }
}

Ответ:

{
  "data": {
    "Author": {
      "addBook": 4,
      "updateBook": {
        "authorId": 4,
        "title": "Flumwitz the Magnificent"
      }
    }
  }
}

Как насчет “книги”?

Вы можете заметить, что на самом деле есть субконтестку в Play. Обратите внимание, что у нас есть мутации по имени Доклад , updatebook , Удалить книгу Отказ Я мог бы отразить это в схеме:

type AuthorOps {
     Book: BookOps
}

type BookOps {
     add(input: AddBookInput!): Int
     remove(input: RemoveBookInput! ): Boolean
     update(input: UpdateBookInput!): Book
}

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

>>> RESPONSE >>>
{
  "data": {
    "Author": {
       "Book": {

          "add": 4,
          "update": {
             "authorId": 4,
             "title": "Flumwitz the Magnificent"
          }
        }
     }
  }
}

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

Рабочий исходный код для этого поста можно найти На моей учетной записи GitHub Отказ