С помощью Cloud Functions вы можете развернуть код для обработки событий, вызванных изменениями в базе данных Cloud Firestore. Это позволяет легко добавлять в приложение функции на стороне сервера, не запуская собственные серверы.
Cloud Functions (второе поколение)
Cloud Functions for Firebase второго поколения работает на базе Cloud Run и Eventarc. Это решение предоставляет более мощную инфраструктуру, расширенные возможности управления производительностью и масштабируемостью, а также больше контроля над средой выполнения функций. Подробнее о Cloud Functions for Firebase второго поколения… Чтобы узнать больше о первом поколении, прочитайте статью Как расширить возможности Cloud Firestore с помощью Cloud Functions.
Триггеры функций 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 с дополнительной информацией для аутентификации |
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 с помощью триггера события "Документ записан". Эта функция срабатывает, если пользователь создан, обновлен или удален:
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
onDocumentCreatedWithAuthContextonDocumentWrittenWithAuthContextonDocumentDeletedWithAuthContextonDocumentUpdatedWithAuthContext
Python
on_document_created_with_auth_contexton_document_updated_with_auth_contexton_document_deleted_with_auth_contexton_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 (первого поколения) требуется существующая база данных (default) в стандартном режиме Firestore. Именованные базы данных Cloud Firestore и режим хранилища данных не поддерживаются. В таких случаях для настройки событий используйте Cloud Functions (второе поколение).
- Настройка нескольких проектов с триггерами Cloud Functions и Cloud Firestore не поддерживается. Чтобы настроить триггер Cloud Firestore, Cloud Functions должен быть в том же проекте.
- Порядок не гарантируется. Быстрые изменения могут привести к вызову функций в неожиданном порядке.
- События доставляются по крайней мере один раз, но одно событие может привести к нескольким вызовам функций. Не полагайтесь на механику однократного выполнения и пишите идемпотентные функции.
- Для Cloud Firestore в режиме Datastore требуется Cloud Functions (второе поколение). Cloud Functions первого поколения не поддерживает режим Datastore.
- Триггер связан с одной базой данных. Нельзя создать триггер, который соответствует нескольким базам данных.
- При удалении базы данных триггеры для нее не удаляются автоматически. Триггер перестанет передавать события, но будет существовать, пока вы его не удалите.
- Если размер сопоставленного события превышает максимальный размер запроса, событие может не быть доставлено в Cloud Functions (первое поколение).
- События, которые не были доставлены из-за размера запроса, регистрируются в журналах платформы и учитываются при подсчете использования журналов для проекта.
- В Проводнике журналов вы можете найти записи с сообщением "Event cannot deliver to Cloud function due to size exceeding the limit for 1st gen..." (Не удалось доставить событие в функцию Cloud Functions, поскольку его размер превышает ограничение для первого поколения...) и уровнем серьезности
error. Название функции можно найти в полеfunctionName. Если полеreceiveTimestampпо-прежнему находится в пределах часа от текущего момента, вы можете определить фактическое содержимое события, прочитав соответствующий документ со снимком до и после временной метки. - Чтобы избежать этого, вы можете:
- Перейдите на Cloud Functions второго поколения
- Уменьшить размер документа
- Удалите Cloud Functions.
- Вы можете отключить ведение журнала с помощью исключений, но обратите внимание, что события, нарушающие правила, по-прежнему не будут доставляться.