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

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

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

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

  • Развертываемые мутации — это те, которые вы реализуете для вызова из клиентских приложений в коннекторе с определяемыми вами конечными точками API. Data Connect интегрирует аутентификацию и авторизацию в эти мутации и генерирует клиентские SDK на основе вашего API.
  • Административные мутации ad hoc запускаются из привилегированных сред для заполнения и управления таблицами. Вы можете создавать и выполнять их в консоли 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: часто имеет смысл применять такой оператор, как инкремент, для обновления значений.

Чтобы изменить предыдущий пример обновления, предположим, что вы хотите увеличить рейтинг конкретного фильма. Для этого можно использовать синтаксис 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 Мутации Обеспечивает выполнение мутации всегда в транзакции базы данных. См. Многошаговые операции .

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

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