Расширение совместимости Cloud Firestore с MongoDB с Cloud Functions (2-го поколения)

С помощью Cloud Functions вы можете развернуть код для обработки событий, вызванных изменениями в базе данных Cloud Firestore, совместимой с MongoDB. Это позволяет добавлять серверную функциональность в приложение без необходимости запуска собственных серверов.

Cloud Functions (2-го поколения)

Cloud Functions for Firebase (2-го поколения), работающий на базе Cloud Run и Eventarc , предоставляет более мощную инфраструктуру, расширенный контроль над производительностью и масштабируемостью, а также более точный контроль над средой выполнения функций. Подробнее о Cloud Functions for Firebase (2-го поколения) см. в статье Cloud Functions for Firebase (2-го поколения) .

Cloud Firestore с функцией совместимости MongoDB

Пакет SDK Cloud Functions for Firebase экспортирует следующие триггеры событий совместимости Cloud Firestore с MongoDB, позволяя создавать обработчики, привязанные к определенным событиям совместимости Cloud Firestore с MongoDB:

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 с MongoDB срабатывают только при изменении документа. Обновление документа совместимости Cloud Firestore с MongoDB, данные в котором не изменялись (запись без операции), не генерирует событие обновления или записи. Невозможно добавлять события к определённым полям.

Если у вас еще нет проекта с поддержкой Cloud Functions for Firebase , прочтите статью Начало работы с Cloud Functions for Firebase (2-го поколения), чтобы настроить и оборудовать свой проект Cloud Functions for Firebase .

Написание Cloud Firestore с функциями, совместимыми с MongoDB

Определить триггер функции

Чтобы определить триггер совместимости Cloud Firestore с MongoDB, укажите путь к документу и тип события:

Node.js

const {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} = require('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

const {
  onDocumentWritten,
  Change,
  FirestoreEvent
} = require('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

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"}
});

Питон

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 .

Совпадения с подстановочными знаками извлекаются из пути к документу и сохраняются в event.params . Триггер всегда должен указывать на документ, даже если вы используете подстановочные знаки.

Триггеры событий

Запустить функцию при создании нового документа

Вы можете активировать функцию, которая будет срабатывать каждый раз при создании нового документа в коллекции. Этот пример функции срабатывает каждый раз при добавлении нового профиля пользователя:

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 .

Питон

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 .

Питон

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 .

Питон

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 и MongoDB, с помощью триггера события «Documentwritten». Этот пример функции срабатывает при создании, обновлении или удалении пользователя:

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 .

Питон

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, совместимой с MongoDB. Вы можете получить доступ к этому документу в снимке, возвращаемом функцией.

Ссылка на документ включает такие методы, как 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});

});

Питон

@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

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}); 
}); 

Питон

@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({
      # ...
  })

Ограничения

  • Порядок не гарантируется. Быстрые изменения могут привести к вызову функций в неожиданном порядке.
  • События доставляются как минимум один раз, но одно событие может привести к нескольким вызовам функций. Избегайте зависимости от механики «точно один раз» и пишите идемпотентные функции .
  • Триггер связан с одной базой данных. Невозможно создать триггер, соответствующий нескольким базам данных.
  • Удаление базы данных не приводит к автоматическому удалению всех триггеров в этой базе данных. Триггер перестаёт отправлять события, но продолжает существовать до тех пор, пока вы его не удалите .