Cloud Firestore 트리거


Cloud Functions를 사용하면 클라이언트 코드를 업데이트하지 않고도 Cloud Firestore의 이벤트를 처리할 수 있습니다. 문서 스냅샷 인터페이스 또는 Admin SDK를 통해 Cloud Firestore를 변경할 수 있습니다.

일반적인 수명 주기에서 Cloud Firestore 함수는 다음을 수행합니다.

  1. 특정 문서가 변경되기를 기다립니다.
  2. 이벤트가 발생하면 트리거되어 작업을 수행합니다.
  3. 지정된 문서에 저장된 데이터의 스냅샷이 포함된 데이터 객체를 수신합니다. 쓰기 또는 업데이트 이벤트의 경우 데이터 객체에 이벤트 트리거 전후의 데이터 상태를 나타내는 스냅샷 2개가 포함됩니다.

Firestore 인스턴스의 위치와 함수의 위치 간의 거리로 인해 상당한 네트워크 지연 시간이 발생할 수 있습니다. 이 경우 성능을 최적화하려면 함수 위치를 지정해 보세요.

Cloud Firestore 함수 트리거

Cloud Functions for Firebase SDK는 특정 Cloud Firestore 이벤트에 연결된 핸들러를 만들 수 있는 functions.firestore 객체를 내보냅니다.

이벤트 유형 트리거
onCreate 문서를 처음으로 기록할 때 트리거됩니다.
onUpdate 이미 존재하는 문서에서 값이 변경되었을 때 트리거됩니다.
onDelete 데이터가 있는 문서가 삭제되면 트리거됩니다.
onWrite onCreate, onUpdate 또는 onDelete가 트리거될 때 트리거됩니다.

Cloud Functions for Firebase가 사용 설정된 프로젝트가 없으면 시작하기: 첫 번째 Firebase Functions 작성 및 배포를 읽고 Cloud Functions for Firebase 프로젝트를 구성 및 설정하세요.

Cloud Firestore에서 트리거되는 함수 작성

함수 트리거 정의

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

Node.js

const functions = require('firebase-functions');

exports.myFunction = functions.firestore
  .document('my-collection/{docId}')
  .onWrite((change, context) => { /* ... */ });

문서 경로는 특정 문서 또는 와일드 카드 패턴을 참조할 수 있습니다.

단일 문서 지정

다음 함수를 사용하면 특정 문서의 모든 변경에 이벤트를 트리거할 수 있습니다.

Node.js

// Listen for any change on document `marie` in collection `users`
exports.myFunctionName = functions.firestore
    .document('users/marie').onWrite((change, context) => {
      // ... Your code here
    });

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

특정 컬렉션의 문서 등 한 문서 그룹에 트리거를 연결하려면 문서 ID 대신 {wildcard}를 사용하면 됩니다.

Node.js

// Listen for changes in all documents in the 'users' collection
exports.useWildcard = functions.firestore
    .document('users/{userId}')
    .onWrite((change, context) => {
      // If we set `/users/marie` to {name: "Marie"} then
      // context.params.userId == "marie"
      // ... and ...
      // change.after.data() == {name: "Marie"}
    });

이 예시에서는 users의 문서에서 필드가 하나라도 변경되면 userId라는 와일드 카드와 일치시킵니다.

users의 문서에 하위 컬렉션이 있고, 이 하위 컬렉션 문서 중 하나에서 필드가 변경되면 userId 와일드 카드가 트리거되지 않습니다.

와일드 카드 일치는 문서 경로에서 추출되어 context.params에 저장됩니다. 개수 제한 없이 원하는 만큼 와일드 카드를 정의하여 명시적인 컬렉션 또는 문서 ID를 대체할 수 있습니다. 예를 들면 다음과 같습니다.

Node.js

// Listen for changes in all documents in the 'users' collection and all subcollections
exports.useMultipleWildcards = functions.firestore
    .document('users/{userId}/{messageCollectionId}/{messageId}')
    .onWrite((change, context) => {
      // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
      // context.params.userId == "marie";
      // context.params.messageCollectionId == "incoming_messages";
      // context.params.messageId == "134";
      // ... and ...
      // change.after.data() == {body: "Hello"}
    });

이벤트 트리거

새 문서가 생성될 때 함수 트리거

와일드 카드와 함께 onCreate() 핸들러를 사용하면 컬렉션에 새 문서가 생성될 때마다 함수를 트리거하여 실행할 수 있습니다. 다음은 새 사용자 프로필이 추가될 때마다 createUser를 호출하는 함수의 예시입니다.

Node.js

exports.createUser = functions.firestore
    .document('users/{userId}')
    .onCreate((snap, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = snap.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

문서가 업데이트될 때 함수 트리거

와일드 카드와 함께 onUpdate() 함수를 사용하면 문서가 업데이트될 때 함수를 트리거하여 실행할 수 있습니다. 다음은 사용자가 프로필을 변경하면 updateUser를 호출하는 함수의 예시입니다.

Node.js

exports.updateUser = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the document
      // e.g. {'name': 'Marie', 'age': 66}
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();

      // access a particular field as you would any JS property
      const name = newValue.name;

      // perform desired operations ...
    });

문서가 삭제될 때 함수 트리거

와일드 카드와 함께 onDelete() 함수를 사용하면 문서가 삭제될 때 함수를 트리거할 수 있습니다. 다음은 사용자가 자신의 사용자 프로필을 삭제하면 deleteUser를 호출하는 함수의 예시입니다.

Node.js

exports.deleteUser = functions.firestore
    .document('users/{userID}')
    .onDelete((snap, context) => {
      // Get an object representing the document prior to deletion
      // e.g. {'name': 'Marie', 'age': 66}
      const deletedValue = snap.data();

      // perform desired operations ...
    });

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

실행되는 이벤트 유형에 관계없이 Cloud Firestore 문서의 모든 변경사항을 수신 대기하려면 와일드 카드와 함께 onWrite() 함수를 사용합니다. 다음은 사용자가 생성, 업데이트 또는 삭제될 때 modifyUser를 호출하는 함수의 예시입니다.

Node.js

exports.modifyUser = functions.firestore
    .document('users/{userID}')
    .onWrite((change, context) => {
      // Get an object with the current document value.
      // If the document does not exist, it has been deleted.
      const document = change.after.exists ? change.after.data() : null;

      // Get an object with the previous document value (for update or delete)
      const oldDocument = change.before.data();

      // perform desired operations ...
    });

데이터 읽기 및 쓰기

함수가 트리거되면 이벤트와 관련된 데이터 스냅샷이 제공됩니다. 이 스냅샷을 사용하면 이벤트를 트리거한 문서에 읽기 또는 쓰기를 수행하거나 Firebase Admin SDK를 사용해 데이터베이스의 다른 부분에 액세스할 수 있습니다.

이벤트 데이터

데이터 읽기

함수가 트리거될 때 업데이트된 문서의 데이터 또는 업데이트되기 전의 데이터를 가져와야 할 수 있습니다. 이전 데이터를 가져오려면 업데이트 전의 문서 스냅샷을 포함하는 change.before.data()를 사용합니다. 마찬가지로 change.after.data()는 업데이트 후의 문서 스냅샷 상태를 포함합니다.

Node.js

exports.updateUser2 = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Get an object representing the current document
      const newValue = change.after.data();

      // ...or the previous value before this update
      const previousValue = change.before.data();
    });

다른 객체와 마찬가지로 속성에 액세스할 수 있습니다. 또는 get 함수를 사용하여 특정 필드에 액세스할 수 있습니다.

Node.js

// Fetch data using standard accessors
const age = snap.data().age;
const name = snap.data()['name'];

// Fetch data using built in accessor
const experience = snap.get('experience');

데이터 쓰기

각 함수 호출은 Cloud Firestore 데이터베이스의 특정 문서와 연관됩니다. 함수로 반환되는 스냅샷의 ref 속성에 있는 DocumentReference로 이 문서에 액세스할 수 있습니다.

Cloud Firestore Node.js SDK에서 제공하는 이 DocumentReference에는 update(), set(), remove() 등의 메서드가 포함되므로 함수를 트리거한 문서를 손쉽게 수정할 수 있습니다.

Node.js

// Listen for updates to any `user` document.
exports.countNameChanges = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
      // Retrieve the current and previous value
      const data = change.after.data();
      const previousData = change.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 change.after.ref.set({
        name_change_count: count + 1
      }, {merge: true});
    });

트리거 이벤트 외부의 데이터

Cloud Functions는 프로젝트의 서비스 계정으로 승인되기 때문에 신뢰할 수 있는 환경에서 실행됩니다. Firebase Admin SDK를 사용해 읽기 및 쓰기를 수행할 수 있습니다.

Node.js

const admin = require('firebase-admin');
admin.initializeApp();

const db = admin.firestore();

exports.writeToFirestore = functions.firestore
  .document('some/doc')
  .onWrite((change, context) => {
    db.doc('some/otherdoc').set({ ... });
  });

제한사항

Cloud Functions용 Cloud 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 삭제
    • 제외를 사용하여 로깅 자체를 사용 중지할 수 있지만 그래도 문제가 되는 이벤트가 전송되지 않습니다.