С помощью Cloud Functions вы можете обрабатывать события в Cloud Firestore без необходимости обновления клиентского кода. Изменения в Cloud Firestore можно вносить через интерфейс снимков документа или через Admin SDK .
В типичном жизненном цикле функция Cloud Firestore выполняет следующие действия:
- Ожидает изменений в определенном документе.
- Срабатывает при возникновении события и выполняет свои задачи.
- Получает объект данных, содержащий моментальный снимок данных, хранящихся в указанном документе. Для событий записи или обновления объект данных содержит два моментальных снимка, представляющих состояние данных до и после события-триггера.
Расстояние между экземпляром Firestore и местоположением функции может привести к значительной задержке в сети. Для оптимизации производительности рекомендуется указывать местоположение функции везде, где это применимо.
Триггеры функции Cloud Firestore
Пакет SDK Cloud Functions for Firebase экспортирует следующие триггеры событий Cloud Firestore, позволяя создавать обработчики, привязанные к определенным событиям Cloud Firestore:
Node.js
Тип события | Курок |
---|---|
onDocumentCreated | Срабатывает при первой записи в документ. |
onDocumentUpdated | Срабатывает, когда документ уже существует и в нем изменилось какое-либо значение. |
onDocumentDeleted | Срабатывает при удалении документа. |
onDocumentWritten | Срабатывает при срабатывании onDocumentCreated , onDocumentUpdated или onDocumentDeleted . |
onDocumentCreatedWithAuthContext | onDocumentCreated с дополнительной информацией об аутентификации |
onDocumentWrittenWithAuthContext | onDocumentWritten с дополнительной информацией об аутентификации |
onDocumentDeletedWithAuthContext | onDocumentDeleted с дополнительной информацией об аутентификации |
onDocumentUpdatedWithAuthContext | onDocumentUpdated с дополнительной информацией об аутентификации |
Питон
Тип события | Курок |
---|---|
on_document_created | Срабатывает при первой записи в документ. |
on_document_updated | Срабатывает, когда документ уже существует и в нем изменилось какое-либо значение. |
on_document_deleted | Срабатывает при удалении документа. |
on_document_written | Срабатывает при срабатывании on_document_created , on_document_updated или on_document_deleted . |
on_document_created_with_auth_context | on_document_created с дополнительной информацией об аутентификации |
on_document_updated_with_auth_context | on_document_updated с дополнительной информацией об аутентификации |
on_document_deleted_with_auth_context | on_document_deleted с дополнительной информацией об аутентификации |
on_document_written_with_auth_context | on_document_written с дополнительной информацией об аутентификации |
События Cloud Firestore срабатывают только при изменении документа. Обновление документа Cloud Firestore, данные в котором не изменялись (запись без операции), не генерирует событие обновления или записи. Невозможно добавлять события к определённым полям.
Если у вас еще нет проекта с поддержкой Cloud Functions for Firebase , прочтите статью Начало работы с Cloud Functions for Firebase (2-го поколения), чтобы настроить и оборудовать свой проект Cloud Functions for Firebase .
Написание функций, запускаемых Cloud Firestore
Определить триггер функции
Чтобы определить триггер Cloud Firestore, укажите путь к документу и тип события:
Node.js
import {
onDocumentWritten,
onDocumentCreated,
onDocumentUpdated,
onDocumentDeleted,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
/* ... */
});
Питон
from firebase_functions.firestore_fn import (
on_document_created,
on_document_deleted,
on_document_updated,
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
Пути к документам могут ссылаться либо на конкретный документ , либо на подстановочный шаблон .
Укажите один документ
Если вы хотите инициировать событие для любого изменения в определенном документе, то вы можете использовать следующую функцию.
Node.js
import {
onDocumentWritten,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.myfunction = onDocumentWritten("users/marie", (event) => {
// Your code here
});
Питон
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/marie")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
Укажите группу документов с помощью подстановочных знаков
Если вы хотите прикрепить триггер к группе документов, например, к любому документу в определенной коллекции, то вместо идентификатора документа используйте {wildcard}
:
Node.js
import {
onDocumentWritten,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.myfunction = onDocumentWritten("users/{userId}", (event) => {
// If we set `/users/marie` to {name: "Marie"} then
// event.params.userId == "marie"
// ... and ...
// event.data.after.data() == {name: "Marie"}
});
Питон
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# If we set `/users/marie` to {name: "Marie"} then
event.params["userId"] == "marie" # True
# ... and ...
event.data.after.to_dict() == {"name": "Marie"} # True
В этом примере при изменении любого поля в документе users
оно сопоставляется с подстановочным знаком userId
.
Если документ в users
имеет подколлекции и поле в одном из документов этих подколлекций изменяется, подстановочный знак userId
не активируется.
Подстановочные знаки извлекаются из пути к документу и сохраняются в event.params
. Вы можете определить любое количество подстановочных знаков для замены явных идентификаторов коллекций или документов, например:
Node.js
import {
onDocumentWritten,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.myfunction = onDocumentWritten("users/{userId}/{messageCollectionId}/{messageId}", (event) => {
// If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
// event.params.userId == "marie";
// event.params.messageCollectionId == "incoming_messages";
// event.params.messageId == "134";
// ... and ...
// event.data.after.data() == {body: "Hello"}
});
Питон
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/{userId}/{messageCollectionId}/{messageId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
event.params["userId"] == "marie" # True
event.params["messageCollectionId"] == "incoming_messages" # True
event.params["messageId"] == "134" # True
# ... and ...
event.data.after.to_dict() == {"body": "Hello"}
Ваш триггер всегда должен указывать на документ, даже если вы используете подстановочный знак. Например, users/{userId}/{messageCollectionId}
недопустим, поскольку {messageCollectionId}
— это коллекция. Однако users/{userId}/{messageCollectionId}/{messageId}
допустим, поскольку {messageId}
всегда будет указывать на документ.
Триггеры событий
Запустить функцию при создании нового документа
Вы можете активировать функцию, которая будет срабатывать каждый раз при создании нового документа в коллекции. Этот пример функции срабатывает каждый раз при добавлении нового профиля пользователя:
Node.js
import {
onDocumentCreated,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.createuser = onDocumentCreated("users/{userId}", (event) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const snapshot = event.data;
if (!snapshot) {
console.log("No data associated with the event");
return;
}
const data = snapshot.data();
// access a particular field as you would any JS property
const name = data.name;
// perform more operations ...
});
Для получения дополнительной информации об аутентификации используйте onDocumentCreatedWithAuthContext
.
Питон
from firebase_functions.firestore_fn import (
on_document_created,
Event,
DocumentSnapshot,
)
@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
# Get a dictionary representing the document
# e.g. {'name': 'Marie', 'age': 66}
new_value = event.data.to_dict()
# Access a particular field as you would any dictionary
name = new_value["name"]
# Perform more operations ...
Запустить функцию при обновлении документа
Вы также можете активировать функцию, которая будет срабатывать при обновлении документа. В этом примере функция срабатывает, если пользователь меняет свой профиль:
Node.js
import {
onDocumentUpdated,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.updateuser = onDocumentUpdated("users/{userId}", (event) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const newValue = event.data.after.data();
// access a particular field as you would any JS property
const name = newValue.name;
// perform more operations ...
});
Для получения дополнительной информации об аутентификации используйте onDocumentUpdatedWithAuthContext
.
Питон
from firebase_functions.firestore_fn import (
on_document_updated,
Event,
Change,
DocumentSnapshot,
)
@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get a dictionary representing the document
# e.g. {'name': 'Marie', 'age': 66}
new_value = event.data.after.to_dict()
# Access a particular field as you would any dictionary
name = new_value["name"]
# Perform more operations ...
Запустить функцию при удалении документа
Вы также можете активировать функцию при удалении документа. Эта функция срабатывает, когда пользователь удаляет свой профиль:
Node.js
import {
onDocumentDeleted,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.deleteuser = onDocumentDeleted("users/{userId}", (event) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const snap = event.data;
const data = snap.data();
// perform more operations ...
});
Для получения дополнительной информации об аутентификации используйте onDocumentDeletedWithAuthContext
.
Питон
from firebase_functions.firestore_fn import (
on_document_deleted,
Event,
DocumentSnapshot,
)
@on_document_deleted(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot|None]) -> None:
# Perform more operations ...
Запустить функцию для всех изменений в документе
Если тип события не важен, вы можете отслеживать все изменения в документе Cloud Firestore, используя триггер события «Document written». Этот пример функции срабатывает при создании, обновлении или удалении пользователя:
Node.js
import {
onDocumentWritten,
Change,
FirestoreEvent
} from "firebase-functions/v2/firestore";
exports.modifyuser = onDocumentWritten("users/{userId}", (event) => {
// Get an object with the current document values.
// If the document does not exist, it was deleted
const document = event.data.after.data();
// Get an object with the previous document values
const previousValues = event.data.before.data();
// perform more operations ...
});
Для получения дополнительной информации об аутентификации используйте onDocumentWrittenWithAuthContext
.
Питон
from firebase_functions.firestore_fn import (
on_document_written,
Event,
Change,
DocumentSnapshot,
)
@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
# Get an object with the current document values.
# If the document does not exist, it was deleted.
document = (event.data.after.to_dict()
if event.data.after is not None else None)
# Get an object with the previous document values.
# If the document does not exist, it was newly created.
previous_values = (event.data.before.to_dict()
if event.data.before is not None else None)
# Perform more operations ...
Чтение и запись данных
При срабатывании функции создается снимок данных, связанных с событием. Вы можете использовать этот снимок для чтения или записи данных в документ, вызвавший событие, или использовать Firebase Admin SDK для доступа к другим частям базы данных.
Данные о событиях
Чтение данных
При срабатывании функции может потребоваться получить данные из документа, который был обновлён, или данные до обновления. Получить предыдущие данные можно с помощью event.data.before
, который содержит снимок документа до обновления. Аналогично, event.data.after
содержит снимок состояния документа после обновления.
Node.js
exports.updateuser2 = onDocumentUpdated("users/{userId}", (event) => {
// Get an object with the current document values.
// If the document does not exist, it was deleted
const newValues = event.data.after.data();
// Get an object with the previous document values
const previousValues = event.data.before.data();
});
Питон
@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get an object with the current document values.
new_value = event.data.after.to_dict()
# Get an object with the previous document values.
prev_value = event.data.before.to_dict()
Вы можете получить доступ к свойствам так же, как к любому другому объекту. Кроме того, для доступа к определённым полям можно использовать функцию get
:
Node.js
// Fetch data using standard accessors
const age = event.data.after.data().age;
const name = event.data.after.data()['name'];
// Fetch data using built in accessor
const experience = event.data.after.data.get('experience');
Питон
# Get the value of a single document field.
age = event.data.after.get("age")
# Convert the document to a dictionary.
age = event.data.after.to_dict()["age"]
Запись данных
Каждый вызов функции связан с определённым документом в базе данных Cloud Firestore. Вы можете получить доступ к этому документу в снимке состояния, возвращаемом функцией.
Ссылка на документ включает такие методы, как update()
, set()
и remove()
чтобы вы могли изменить документ, вызвавший эту функцию.
Node.js
import { onDocumentUpdated } from "firebase-functions/v2/firestore";
exports.countnamechanges = onDocumentUpdated('users/{userId}', (event) => {
// Retrieve the current and previous value
const data = event.data.after.data();
const previousData = event.data.before.data();
// We'll only update if the name has changed.
// This is crucial to prevent infinite loops.
if (data.name == previousData.name) {
return null;
}
// Retrieve the current count of name changes
let count = data.name_change_count;
if (!count) {
count = 0;
}
// Then return a promise of a set operation to update the count
return data.after.ref.set({
name_change_count: count + 1
}, {merge: true});
});
Питон
@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get the current and previous document values.
new_value = event.data.after
prev_value = event.data.before
# We'll only update if the name has changed.
# This is crucial to prevent infinite loops.
if new_value.get("name") == prev_value.get("name"):
return
# Retrieve the current count of name changes
count = new_value.to_dict().get("name_change_count", 0)
# Update the count
new_value.reference.update({"name_change_count": count + 1})
Доступ к информации об аутентификации пользователя
При использовании одного из следующих типов событий вы можете получить доступ к информации об аутентификации пользователя, инициировавшего событие. Эта информация дополняет информацию, возвращаемую в базовом событии.
Node.js
-
onDocumentCreatedWithAuthContext
-
onDocumentWrittenWithAuthContext
-
onDocumentDeletedWithAuthContext
-
onDocumentUpdatedWithAuthContext
Питон
-
on_document_created_with_auth_context
-
on_document_updated_with_auth_context
-
on_document_deleted_with_auth_context
-
on_document_written_with_auth_context
Информацию о данных, доступных в контексте аутентификации, см. в разделе Контекст аутентификации . В следующем примере показано, как получить информацию об аутентификации:
Node.js
import { onDocumentWrittenWithAuthContext } from "firebase-functions/v2/firestore"
exports.syncUser = onDocumentWrittenWithAuthContext("users/{userId}", (event) => {
const snapshot = event.data.after;
if (!snapshot) {
console.log("No data associated with the event");
return;
}
const data = snapshot.data();
// retrieve auth context from event
const { authType, authId } = event;
let verified = false;
if (authType === "system") {
// system-generated users are automatically verified
verified = true;
} else if (authType === "unknown" || authType === "unauthenticated") {
// admin users from a specific domain are verified
if (authId.endsWith("@example.com")) {
verified = true;
}
}
return data.after.ref.set({
created_by: authId,
verified,
}, {merge: true});
});
Питон
@on_document_updated_with_auth_context(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
# Get the current and previous document values.
new_value = event.data.after
prev_value = event.data.before
# Get the auth context from the event
user_auth_type = event.auth_type
user_auth_id = event.auth_id
Данные за пределами события-триггера
Cloud Functions выполняются в доверенной среде. Они авторизованы как сервисная учётная запись в вашем проекте, и вы можете выполнять чтение и запись с помощью Firebase Admin SDK :
Node.js
const { initializeApp } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');
initializeApp();
const db = getFirestore();
exports.writetofirestore = onDocumentWritten("some/doc", (event) => {
db.doc('some/otherdoc').set({ ... });
});
exports.writetofirestore = onDocumentWritten('users/{userId}', (event) => {
db.doc('some/otherdoc').set({
// Update otherdoc
});
});
Питон
from firebase_admin import firestore, initialize_app
import google.cloud.firestore
initialize_app()
@on_document_written(document="some/doc")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
firestore_client: google.cloud.firestore.Client = firestore.client()
firestore_client.document("another/doc").set({
# ...
})
Ограничения
Обратите внимание на следующие ограничения для триггеров Cloud Firestore для Cloud Functions:
- Для работы Cloud Functions (1-го поколения) требуется существующая база данных (default) в собственном режиме Firestore. Она не поддерживает именованные базы данных Cloud Firestore или режим Datastore. В таких случаях для настройки событий используйте Cloud Functions (2-го поколения).
- Порядок не гарантируется. Быстрые изменения могут привести к вызову функций в неожиданном порядке.
- События доставляются как минимум один раз, но одно событие может привести к нескольким вызовам функций. Избегайте зависимости от механики «точно один раз» и пишите идемпотентные функции .
- Для работы Cloud Firestore в режиме хранилища данных требуется Cloud Functions (2-го поколения). Cloud Functions (1-го поколения) не поддерживает режим хранилища данных.
- Триггер связан с одной базой данных. Вы не можете создать триггер, соответствующий нескольким базам данных.
- Удаление базы данных не приводит к автоматическому удалению всех триггеров для этой базы данных. Триггер перестаёт отправлять события, но продолжает существовать до тех пор, пока вы его не удалите .
- Если размер сопоставленного события превышает максимальный размер запроса , событие может быть не доставлено в Cloud Functions (1-го поколения).
- События, не доставленные из-за размера запроса, регистрируются в журналах платформы и учитываются при расчете использования журнала для проекта.
- Эти журналы можно найти в обозревателе журналов с сообщением «Событие не может быть доставлено в облачную функцию из-за превышения лимита размера для 1-го поколения...» с указанием уровня серьёзности
error
. Имя функции можно найти в полеfunctionName
. Если значение поляreceiveTimestamp
всё ещё не истекло в течение часа, вы можете определить фактическое содержимое события, прочитав соответствующий документ со снимком до и после временной метки. - Чтобы избежать такой каденции, вы можете:
- Миграция и обновление до Cloud Functions (2-го поколения)
- Уменьшить размер документа
- Удалить соответствующие Cloud Functions
- Вы можете отключить ведение журнала с помощью исключений , но учтите, что нежелательные события все равно не будут доставлены.