Внедрение мутаций Data Connect

Firebase Data Connect позволяет создавать коннекторы для ваших экземпляров PostgreSQL, управляемых с помощью Google Cloud SQL. Эти коннекторы представляют собой комбинации запросов и мутаций для использования данных из вашей схемы.

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

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

  • Развертываемые мутации — это те, которые вы реализуете для вызова из клиентских приложений в коннекторе с помощью определяемых вами конечных точек API. Data Connect интегрирует аутентификацию и авторизацию в эти мутации и генерирует клиентские SDK на основе вашего API.
  • Внеплановые административные изменения выполняются из привилегированных сред для заполнения и управления таблицами. Вы можете создавать и выполнять их в консоли Firebase , из привилегированных сред с помощью Firebase Admin SDK , а также в локальных средах разработки с помощью нашего расширения Data Connect для VS Code.

В этом руководстве более подробно рассматриваются развертываемые мутации .

Особенности мутаций Data Connect

Data Connect позволяет выполнять базовые операции изменения данных всеми способами, которые вы ожидаете от базы данных PostgreSQL:

  • Выполняйте операции CRUD.
  • Управление многоэтапными операциями с помощью транзакций.

Однако благодаря расширениям Data Connect для GraphQL вы можете реализовать сложные мутации для создания более быстрых и эффективных приложений:

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

Используйте сгенерированные поля для реализации мутаций.

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

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

Предположим, ваша схема содержит тип Movie и связанный с ним тип Actor . Data Connect генерирует поля типа movie_insert , movie_update , movie_delete и другие.

Мутация с
movie_insert

Поле movie_insert представляет собой мутацию, создающую единственную запись в таблице Movie .

Используйте это поле для создания одного фильма.

mutation CreateMovie($data: Movie_Data!) {
  movie_insert(data: $data) { key }
}

Мутация с
поле movie_update

Поле movie_update представляет собой мутацию, обновляющую одну запись в таблице Movie .

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

mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) {
  movie_update(key: $myKey, data: $data) { key }
}

Мутация с
поле movie_delete

Поле movie_delete представляет собой мутацию, удаляющую одну запись в таблице Movie .

Используйте это поле для удаления отдельного фильма по его ключу.

  mutation DeleteMovie($myKey: Movie_Key!) {
    movie_delete(key: $myKey) { key }
  }

Основные элементы мутации

Мутации Data Connect — это мутации GraphQL с расширениями Data Connect . Как и в случае с обычной мутацией GraphQL, вы можете определить имя операции и список переменных GraphQL.

Data Connect расширяет возможности запросов GraphQL с помощью настраиваемых директив , таких как @auth и @transaction .

Таким образом, следующая мутация имеет следующий характер:

  • Определение типа mutation
  • Название операции (мутации) SignUp
  • Аргумент операции, представляющий собой единственную переменную $username
  • Одна директива, @auth
  • Одно поле user_insert .
mutation SignUp($username: String!) @auth(level: USER) {
  user_insert(data: {
    id_expr: "auth.uid"
    username: $username
  })
}

Для каждого аргумента мутации требуется объявление типа: встроенного, например, String , или пользовательского, определенного в схеме типа, например, Movie .

Напишите основные мутации

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

Создавать

Давайте займемся базовыми операциями создания.

# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
  movie_insert(data: {
    title: $title
    releaseYear: $releaseYear
    genre: $genre
    rating: $rating
  })
}

# Create a movie with default values
mutation CreateMovie2 {
  movie_insert(data: {
    title: "Sherlock Holmes"
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
  })
}

Или активация.

# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
  movie_upsert(data: {
    title: $title
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
    genre: "Mystery/Thriller"
  })
}

Выполните обновления

Вот последние новости. Продюсеры и режиссёры, безусловно, надеются, что средние рейтинги соответствуют тенденции.

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

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id,
    data: {
      genre: $genre
      rating: $rating
      description: $description
    })
}

Для выполнения нескольких обновлений используйте поле movie_updateMany .

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    data:
      {
        rating: $rating
      })
}

Используйте операции увеличения, уменьшения, добавления и добавления в начало строки с помощью _update

Хотя в мутациях _update и _updateMany можно явно задавать значения в data: часто целесообразнее применять оператор, например, increment, для обновления значений.

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

mutation UpdateMovie(
  $id: UUID!,
  $ratingIncrement: Int!
) {
  movie_update(id: $id, data: {
    rating_update: {
      inc: $ratingIncrement
    }
  })
}

Data Connect поддерживает следующие операторы для обновления полей:

  • inc используется для увеличения значений типов данных Int , Int64 , Float , Date и Timestamp
  • dec — уменьшение значений для типов данных Int , Int64 , Float , Date и Timestamp

Для списков также можно обновлять данные отдельными значениями или списками значений, используя:

  • add элемент(ы) в список, если они еще не присутствуют в списках, за исключением векторных списков.
  • remove — удалить все элементы, если они присутствуют, из списков, кроме векторных списков.
  • append (добавлять) элемент(ы) в списки различных типов, кроме векторных списков.
  • prepend — добавлять элементы в начало списка, за исключением векторных списков.

Выполнить удаление

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

# Delete by key
mutation DeleteMovie($id: UUID!) {
  movie_delete(id: $id)
}

Здесь можно использовать _deleteMany .

# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
  movie_deleteMany(where: { rating: { le: $minRating } })
}

Записывайте мутации в отношениях.

Обратите внимание, как использовать неявную мутацию _upsert для отношения.

# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
  movieMetadata_upsert(
    data: { movie: { id: $movieId }, director: $director }
  )
}

Разрабатывайте схемы для эффективных мутаций.

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

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

Используя значения сервера , вы можете эффективно позволить серверу динамически заполнять поля в ваших таблицах, используя сохраненные или легко вычисляемые значения в соответствии с конкретными выражениями CEL на стороне сервера в аргументе expr . Например, вы можете определить поле с меткой времени, применяемой при доступе к полю, используя время, хранящееся в запросе операции, updatedAt: Timestamp! @default(expr: "request.time") .

Напишите сложные мутации: позвольте Data Connect передавать значения, используя синтаксис field_expr

Как обсуждалось в разделе «Ключевые скалярные значения и значения сервера» , вы можете спроектировать свою схему таким образом, чтобы сервер заполнял значения для общих полей, таких как id и даты, в ответ на запросы клиента.

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

При реализации мутаций используйте синтаксис field_expr для запуска обновлений, генерируемых сервером, или для доступа к данным из запросов. Например, чтобы передать uid авторизации, хранящийся в запросе, в операцию _upsert , передайте "auth.uid" в поле userId_expr .

# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}

# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

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

mutation CreateTodoListWithFirstItem(
  $listName: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
}

Для получения более подробной информации см. скалярные переменные _Expr в справочнике скалярных переменных .

Пишите сложные мутации: многошаговые операции.

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

Data Connect позволяет выполнять многоэтапную логику в ваших мутациях, поддерживая:

  • Несколько полей записи

  • В ваших мутациях можно использовать несколько полей для чтения (с помощью ключевого слова query field).

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

  • Директива @check позволяет оценивать содержимое считываемых данных с помощью выражений CEL и на основе результатов такой оценки:

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

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

    • В директивах @check это делается через аргумент expr: `.
    • При использовании значений сервера применяется синтаксис field_expr

Директива @transaction

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

Директива @transaction гарантирует, что изменение данных — как с одним полем для записи (например, _insert или _update ), так и с несколькими полями для записи — всегда будет выполняться в рамках транзакции базы данных.

  • Мутации без @transaction выполняют каждое корневое поле последовательно. Операция отображает любые ошибки как ошибки частичного заполнения полей, но не показывает последствия последующих выполнений.

  • Мутации с @transaction гарантированно завершатся либо полным успехом, либо полным провалом. Если какое-либо из полей в транзакции завершится неудачей, вся транзакция будет отменена.

Директивы @check и @redact

Директива @check проверяет наличие указанных полей в результатах запроса. Для проверки значений полей используется выражение Common Expression Language (CEL). Поведение директивы по умолчанию заключается в проверке и отклонении узлов, значение которых равно null или [] (пустые списки).

Директива @redact скрывает часть ответа от клиента. Скрытые поля по-прежнему оцениваются на предмет побочных эффектов (включая изменения данных и @check ), и результаты остаются доступными на последующих этапах выражений CEL.

Используйте @check , @check(message:) и @redact

Одно из основных применений аннотаций @check и @redact — поиск связанных данных для принятия решения о том, следует ли авторизовать определенные операции. Поиск осуществляется в логике, но скрывается от клиентов. Ваш запрос может возвращать полезные сообщения для корректной обработки в клиентском коде.

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

query GetMovieEditors($movieId: UUID!) @auth(level: USER) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

Чтобы узнать больше о директивах @check и @redact в проверках авторизации, обратитесь к обсуждению поиска данных авторизации .

Используйте @check для проверки ключей.

Некоторые поля, изменяемые по значению, например _update , могут не выполнять никаких действий, если запись с указанным ключом не существует. Аналогично, запросы могут возвращать значение null или пустой список. Это не считается ошибкой и, следовательно, не приведет к откату изменений.

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

# Delete by key, error if not found
mutation MustDeleteMovie($id: UUID!) @transaction {
  movie_delete(id: $id) @check(expr: "this != null", message: "Movie not found, therefore nothing is deleted")
}

Используйте механизм связывания response для организации цепочки многоступенчатых мутаций.

Основной подход к созданию связанных записей, например, нового Movie и соответствующей записи MovieMetadata , заключается в следующем:

  1. Вызовите мутацию _insert для Movie
  2. Сохраните возвращенный ключ созданного фильма.
  3. Затем вызовите вторую мутацию _insert для создания записи MovieMetadata .

Однако с помощью Data Connect вы можете обработать этот распространенный случай в рамках одной многоэтапной операции, получив доступ к результатам первой операции _insert во второй _insert .

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

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

В следующей мутации списка дел:

  • Привязка response представляет собой частичный объект ответа на данный момент, который включает все поля мутации верхнего уровня, предшествующие текущему.
  • Результаты первоначальной операции todoList_insert , которая возвращает поле id (ключ), доступны позже в response.todoList_insert.id , что позволяет нам немедленно вставить новый пункт в список дел.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1:
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

Используйте response для проверки полей с помощью @check

response также доступна в @check(expr: "...") , поэтому вы можете использовать её для построения ещё более сложной серверной логики. В сочетании с шагами query { … } в мутациях вы можете добиться гораздо большего без дополнительных обращений между клиентом и сервером.

В следующем примере обратите внимание: аннотация @check уже имеет доступ к response.query , поскольку аннотация @check всегда выполняется после шага, к которому она прикреплена.

mutation CreateTodoInNamedList(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1: Look up List.id by its name
  query
  @check(expr: "response.query.todoLists.size() > 0", message: "No such TodoList with the name!")
  @check(expr: "response.query.todoLists.size() < 2", message: "Ambiguous listName!") {
    todoLists(where: { name: $listName }) {
      id
    }
  }
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoLists[0].id" # <-- Now we have the parent list ID to insert to
    content: $itemContent,
  })
}

Для получения более подробной информации о привязке response см. справочник CEL .

Анализ прерванных операций с помощью @transaction и query @check

Многоступенчатые мутации могут приводить к ошибкам:

  • Операции с базой данных могут завершиться неудачей.
  • Логика запроса @check может привести к прекращению операций.

Data Connect рекомендует использовать директиву @transaction для многошаговых мутаций. Это обеспечивает более согласованную базу данных и упрощает обработку результатов мутаций в клиентском коде.

  • При первой же ошибке или сбое @check операция будет завершена, поэтому нет необходимости управлять выполнением каких-либо последующих полей или оценкой CEL.
  • Откат выполняется в ответ на ошибки базы данных или логику @check , обеспечивая согласованное состояние базы данных.
  • В клиентский код всегда возвращается ошибка отката.

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

  • Если проверка одного поля завершается с ошибкой из-за проблем с базой данных, последующие поля продолжат выполняться. Однако ошибки @check ) всё равно приведут к завершению всей операции.
  • Откат изменений не выполняется, что означает смешанное состояние базы данных с некоторыми успешными и некоторыми неудачными обновлениями.
  • Если в логике @check используются результаты операций чтения и/или записи @check выполненных на предыдущем шаге, результаты могут быть более непоследовательными.
  • Результат, возвращаемый клиентскому коду, будет содержать более сложную комбинацию ответов об успехе и неудаче, которые необходимо обработать.

Директивы для изменений Data Connect

В дополнение к директивам, используемым при определении типов и таблиц, Data Connect предоставляет директивы @auth , @check , @redact и @transaction для расширения возможностей выполнения операций.

Директива Применимо к Описание
@auth Запросы и мутации Определяет политику авторизации для запроса или мутации. См. руководство по авторизации и аттестации .
@check поля query в многоэтапных операциях Проверяет наличие указанных полей в результатах запроса. Для проверки значений полей используется выражение Common Expression Language (CEL). См. Многошаговые операции .
@redact Запросы Редактирует часть ответа от клиента. См. Многоэтапные операции .
@transaction Мутации Обеспечивает выполнение каждой операции изменения данных в рамках транзакции базы данных. См. Многошаговые операции .

Следующие шаги

Возможно, вас заинтересует: