Cloud Functions(2세대)로 Cloud Firestore 확장

Cloud Functions를 사용하면 코드를 배포하여 Cloud Firestore 데이터베이스의 변경을 통해 트리거되는 이벤트를 처리할 수 있습니다. 덕분에 자체 서버를 실행하지 않고도 앱에 서버 측 기능을 쉽게 추가할 수 있습니다.

Cloud Functions(2세대)

Cloud RunEventarc 기반 Cloud Functions for Firebase(2세대)를 통해 더 강력한 인프라, 성능 및 확장성에 대한 고급 제어, 함수 런타임에 대한 추가 제어를 사용할 수 있습니다. 2세대에 대한 자세한 내용은 Firebase용 Cloud Functions(2세대)를 참조하세요. 1세대에 대한 자세한 내용은 Cloud Functions로 Cloud Firestore 확장을 참조하세요.

Cloud Firestore 함수 트리거

Cloud Functions for Firebase SDK는 특정 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 문서를 업데이트(노옵스(no-ops) 쓰기)할 때에는 업데이트 또는 쓰기 이벤트가 생성되지 않습니다. 특정 필드에 이벤트를 추가할 수 없습니다.

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) => {
   /* ... */ 
});

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

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "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:

와일드 카드를 사용한 문서 그룹 지정

특정 컬렉션의 문서 등 한 문서 그룹에 트리거를 연결하려면 문서 ID 대신 {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"}
});

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에 저장됩니다. 개수 제한 없이 원하는 만큼 와일드 카드를 정의하여 명시적인 컬렉션 또는 문서 ID를 대체할 수 있습니다. 예를 들면 다음과 같습니다.

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

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

와일드 카드를 사용할 때도 트리거는 항상 문서를 가리켜야 합니다. 예를 들어 {messageCollectionId}는 컬렉션이므로 users/{userId}/{messageCollectionId}는 유효하지 않습니다. 그러나 {messageId}가 항상 문서를 가리키므로 users/{userId}/{messageCollectionId}/{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를 사용하세요.

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

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를 사용하세요.

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

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를 사용하세요.

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

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를 사용하세요.

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

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

});

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

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

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은 신뢰할 수 있는 환경에서 실행됩니다. 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 FunctionsCloud Firestore 트리거의 다음 제한사항에 유의하세요.

  • Cloud Functions(1세대)는 Firestore 기본 모드의 기존 '(기본값)' 데이터베이스를 기본 요건으로 합니다. Cloud Firestore 이름이 지정된 데이터베이스 또는 Datastore 모드는 지원하지 않습니다. 이 경우 Cloud Functions(2세대)를 사용하여 이벤트를 구성하세요.
  • 순서는 보장되지 않습니다. 급격하게 변경하면 예기치 않은 순서로 함수 호출이 트리거될 수 있습니다.
  • 이벤트는 최소 1회 전송되지만 하나의 이벤트에 함수가 여러 번 호출될 수 있습니다. 정확히 한 번에 처리하는 메커니즘에 의존하지 말고 멱등 함수를 작성하세요.
  • Datastore 모드의 Cloud Firestore에는 Cloud Functions(2세대)가 필요합니다. Cloud Functions(1세대)는 Datastore 모드를 지원하지 않습니다.
  • 트리거는 단일 데이터베이스와 연결됩니다. 여러 데이터베이스와 일치하는 트리거를 만들 수 없습니다.
  • 데이터베이스를 삭제해도 해당 데이터베이스의 트리거가 자동으로 삭제되지 않습니다. 트리거가 이벤트 제공을 중지하지만 트리거를 삭제하기 전까지 계속 존재합니다.
  • 일치하는 이벤트가 최대 요청 크기를 초과하면 이벤트가 Cloud Functions(1세대)로 전달되지 않을 수 있습니다.
    • 요청 크기로 인해 전송되지 않은 이벤트는 플랫폼 로그에 로깅되고 프로젝트의 로그 사용량에 반영됩니다.
    • 이러한 로그는 로그 탐색기에서 '크기가 1세대... 한도를 초과하여 Cloud 함수에 이벤트를 전송할 수 없음'이라는 error 심각도의 메시지와 함께 확인할 수 있습니다. functionName 필드 아래에서 함수 이름을 찾을 수 있습니다. receiveTimestamp 필드가 지금부터 1시간 이내인 경우 타임스탬프 전후의 스냅샷과 함께 해당 문서를 읽어 실제 이벤트 콘텐츠를 추론할 수 있습니다.
    • 이러한 주기를 피하려면 다음을 수행하면 됩니다.
      • Cloud Functions(2세대)로 마이그레이션 및 업그레이드
      • 문서 크기 줄이기
      • 문제의 Cloud Functions 삭제
    • 제외를 사용하여 로깅 자체를 사용 중지할 수 있지만 그래도 문제가 되는 이벤트가 전송되지 않습니다.