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
Поле | Используйте это поле для создания одного фильма. mutation CreateMovie($data: Movie_Data!) { movie_insert(data: $data) { key } } |
Мутация с
поле movie_update
Поле | Используйте это поле для обновления информации об отдельном фильме по его ключу. mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) { movie_update(key: $myKey, data: $data) { key } } |
Мутация с
поле movie_delete
Поле | Используйте это поле для удаления отдельного фильма по его ключу. 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 позволяет выполнять многоэтапную логику в ваших мутациях, поддерживая:
Несколько полей записи
В ваших мутациях можно использовать несколько полей для чтения (с помощью ключевого слова
queryfield).Директива
@transactionобеспечивает поддержку транзакций, знакомую по реляционным базам данных.Директива
@checkпозволяет оценивать содержимое считываемых данных с помощью выражений CEL и на основе результатов такой оценки:- Продолжайте создавать, обновлять и удалять элементы, определенные в результате мутации.
- Далее следует возврат результатов запроса.
- Используйте полученные сообщения для выполнения соответствующей логики в коде вашего клиента.
Директива
@redactпозволяет опускать результаты полей запроса из результатов протокола передачи данных.Привязка
responseCEL хранит накопленные результаты всех мутаций и запросов, выполненных в сложной многоэтапной операции. Доступ к привязке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 , заключается в следующем:
- Вызовите мутацию
_insertдляMovie - Сохраните возвращенный ключ созданного фильма.
- Затем вызовите вторую мутацию
_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 | Мутации | Обеспечивает выполнение каждой операции изменения данных в рамках транзакции базы данных. См. Многошаговые операции . |
Следующие шаги
Возможно, вас заинтересует:
- Создание мутаций для ваших приложений с помощью инструментов искусственного интеллекта.
- Авторизация ваших мутаций в соответствии с руководством по авторизации.
- Вызов мутаций из клиентского кода для веб-приложений , iOS , Android и Flutter .
- Выполнение операций с большими объемами данных с помощью мутаций.