С помощью 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 with additional authentication information |
onDocumentWrittenWithAuthContext | onDocumentWritten с дополнительной информацией для аутентификации |
onDocumentDeletedWithAuthContext | onDocumentDeleted с дополнительной информацией для аутентификации |
onDocumentUpdatedWithAuthContext | onDocumentUpdated с дополнительной информацией об аутентификации |
Python
| Тип события | Курок |
|---|---|
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
const {
onDocumentWritten,
onDocumentCreated,
onDocumentUpdated,
onDocumentDeleted,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
/* ... */
});
Python
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
const {
onDocumentWritten,
Change,
FirestoreEvent
} = require('firebase-functions/v2/firestore');
exports.myfunction = onDocumentWritten("users/marie", (event) => {
// Your code here
});
Python
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
const {
onDocumentWritten,
Change,
FirestoreEvent
} = require('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"}
});
Python
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
const {
onDocumentWritten,
Change,
FirestoreEvent
} = require('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"}
});
Python
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
const {
onDocumentCreated,
Change,
FirestoreEvent
} = require('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 .
Python
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
const {
onDocumentUpdated,
Change,
FirestoreEvent
} = require('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 .
Python
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
const {
onDocumentDeleted,
Change,
FirestoreEvent
} = require('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 .
Python
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
const {
onDocumentWritten,
Change,
FirestoreEvent
} = require('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 .
Python
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();
});
Python
@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');
Python
# 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
const {onDocumentUpdated} = require('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 event.data.after.ref.set({
name_change_count: count + 1
}, {merge: true});
});
Python
@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
Python
-
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
const {onDocumentWrittenWithAuthContext} = require('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});
});
Python
@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
});
});
Python
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-го поколения) требуется наличие существующей базы данных "(по умолчанию)" в собственном режиме Firestore. Она не поддерживает именованные базы данных Cloud Firestore или режим Datastore. В таких случаях для настройки событий используйте Cloud Functions (2-го поколения).
- Ограничением является возможность настройки Cloud Functions и триггера Cloud Firestore в рамках одного проекта. Для настройки триггера Cloud Firestore необходимо, чтобы Cloud Functions находились в том же проекте.
- Порядок выполнения не гарантируется. Быстрые изменения могут привести к вызову функций в неожиданном порядке.
- События доставляются как минимум один раз, но одно событие может привести к нескольким вызовам функций. Избегайте зависимости от механизма "точно один раз" и пишите идемпотентные функции .
- Для Cloud Firestore в режиме хранилища данных требуется Cloud Functions (2-го поколения). Cloud Functions (1-го поколения) не поддерживает режим хранилища данных.
- Триггер связан с одной базой данных. Нельзя создать триггер, который бы соответствовал нескольким базам данных.
- Удаление базы данных не приводит к автоматическому удалению всех триггеров для этой базы данных. Триггер перестает генерировать события, но продолжает существовать до тех пор, пока вы его не удалите .
- Если размер соответствующего события превышает максимальный размер запроса , событие может быть не доставлено в Cloud Functions (1-го поколения).
- События, не доставленные из-за недостаточного размера запроса, регистрируются в журналах платформы и учитываются в общем объеме использования журналов для проекта.
- Эти журналы можно найти в обозревателе журналов с сообщением об
error"Не удается передать событие в облачную функцию из-за превышения лимита для первого поколения...". Имя функции можно найти в полеfunctionName. Если значение поляreceiveTimestampвсе еще находится в пределах часа от текущего момента, вы можете определить фактическое содержимое события, прочитав соответствующий документ со снимком до и после метки времени. - Чтобы избежать такой монотонности, вы можете:
- Перейдите на Cloud Functions (2-го поколения) и выполните миграцию.
- Уменьшить размер документа
- Удалите соответствующие Cloud Functions .
- Вы можете отключить само логирование с помощью исключений , но имейте в виду, что проблемные события всё равно не будут доставляться.