Cloud Functions(2세대)로 Firebase 확장

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

Cloud Functions(2세대)

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

Firebase 함수 트리거

Cloud Functions for Firebase SDK는 특정 Firebase 이벤트에 연결된 핸들러를 생성할 수 있도록 다음 Firebase 이벤트 트리거를 내보냅니다.

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

Firebase 이벤트는 문서 변경 시에만 트리거됩니다. 데이터가 변경되지 않는 Firebase 문서를 업데이트(노옵스(no-ops) 쓰기)할 때에는 업데이트 또는 쓰기 이벤트가 생성되지 않습니다. 특정 필드에 이벤트를 추가할 수 없습니다.

Cloud Functions for Firebase가 사용 설정된 프로젝트가 없으면 Cloud Functions for Firebase(2세대) 시작하기를 읽고 Cloud Functions for Firebase 프로젝트를 구성 및 설정하세요.

Firebase 트리거 함수 작성

함수 트리거 정의

Firebase 트리거를 정의하려면 문서 경로와 이벤트 유형을 지정합니다.

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:

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

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

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

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

문서의 모든 변경사항에 대해 함수 트리거

실행되는 이벤트 유형에 관계없이 Firebase 문서의 모든 변경사항을 리슨하려면 '작성된 문서' 이벤트 트리거를 사용합니다. 다음은 사용자가 생성, 업데이트 또는 삭제될 때 실행되는 함수의 예시입니다.

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

데이터 쓰기

각 함수 호출은 Firebase 데이터베이스의 특정 문서와 연결됩니다. 함수로 반환되는 스냅샷에서 해당 문서에 액세스할 수 있습니다.

문서 참조에는 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은 신뢰할 수 있는 환경에서 실행됩니다. 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세대)를 사용하여 이벤트를 구성하세요.
  • Cloud FunctionsCloud Firestore 트리거를 사용한 프로젝트 간 설정은 제한사항입니다. Cloud Firestore 트리거를 설정하려면 Cloud Functions가 동일한 프로젝트에 있어야 합니다.
  • 순서는 보장되지 않습니다. 급격하게 변경하면 예기치 않은 순서로 함수 호출이 트리거될 수 있습니다.
  • 이벤트는 최소 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 삭제
    • 제외를 사용하여 로깅 자체를 사용 중지할 수 있지만 그래도 문제가 되는 이벤트가 전송되지 않습니다.