Cloud Functions를 사용하면 클라이언트 코드를 업데이트하지 않고도 Firebase Realtime Database의 이벤트를 처리할 수 있습니다.
Cloud Functions를 사용하면 전체 관리자 권한으로 Realtime Database 작업을 실행할 수 있으며 Realtime Database의 각 변경사항이 개별적으로 처리됩니다. DataSnapshot
또는 Admin SDK를 통해 Firebase Realtime Database를 변경할 수 있습니다.
일반적인 수명 주기에서 Firebase Realtime Database 함수는 다음을 수행합니다.
- 특정 Realtime Database 위치가 변경되기를 기다립니다.
- 이벤트가 발생할 때 트리거되어 태스크를 수행합니다. 사용 사례의 예시를 더 보려면 Cloud Functions로 무엇을 할 수 있나요?를 참조하세요.
- 지정된 문서에 저장된 데이터의 스냅샷이 포함된 데이터 객체를 수신합니다.
Realtime Database 함수 트리거
functions.database
를 사용하여 Realtime Database 이벤트의 새 함수를 만듭니다. 함수가 트리거되는 시점을 제어하려면 이벤트 핸들러 중 하나를 지정하고 이벤트를 리슨할 Realtime Database 경로를 지정합니다.
이벤트 핸들러 설정
Functions를 사용하면 Realtime Database 이벤트를 처리할 수 있으며 얼마나 세부적으로 처리할지에 따라 2가지 수준으로 나눠집니다. 즉, 생성, 업데이트 또는 삭제 이벤트만 리슨하거나 경로에 대한 모든 유형의 변경사항을 리슨할 수 있습니다. Cloud Functions는 Realtime Database에 대해 다음 이벤트 핸들러를 지원합니다.
onWrite()
- Realtime Database에서 데이터가 생성, 업데이트 또는 삭제될 때 트리거됩니다.onCreate()
- Realtime Database에서 새 데이터가 생성될 때 트리거됩니다.onUpdate()
- Realtime Database에서 데이터가 업데이트될 때 트리거됩니다.onDelete()
- Realtime Database에서 데이터가 삭제될 때 트리거됩니다.
인스턴스 및 경로 지정
함수가 트리거되는 시점과 위치를 제어하려면 ref(path)
를 호출하여 경로를 지정하고 필요에 따라 instance('INSTANCE_NAME')
로 Realtime Database 인스턴스를 지정합니다. 인스턴스를 지정하지 않으면 Firebase 프로젝트의 기본 Realtime Database 인스턴스에 함수가 배포됩니다. 예를 들면 다음과 같습니다.
- 기본 Realtime Database 인스턴스:
functions.database.ref('/foo/bar')
- 이름이 'my-app-db-2'인 인스턴스:
functions.database.instance('my-app-db-2').ref('/foo/bar')
이러한 메서드는 함수가 Realtime Database 인스턴스 내의 특정 경로에서 쓰기를 처리하도록 지시합니다. 경로를 지정하면 해당 경로와 모든 하위 경로에 영향을 주는 모든 쓰기 이벤트와 매칭됩니다. 함수의 경로를 /foo/bar
로 설정하면 다음 두 위치 모두의 이벤트와 매칭됩니다.
/foo/bar
/foo/bar/baz/really/deep/path
두 경우 모두 /foo/bar
에서 이벤트가 발생한 것으로 해석되고, 이벤트 데이터에 /foo/bar
의 이전 데이터와 새 데이터가 포함됩니다. 이벤트 데이터가 큰 경우에는 데이터베이스 루트 근처에 단일 함수를 사용하는 대신 더 깊은 경로에 여러 함수를 사용하는 것이 좋습니다. 성능을 최적화하려면 최대한 깊은 수준의 데이터만 요청합니다.
경로 구성요소를 중괄호로 묶어 와일드 카드로 지정할 수 있습니다. ref('foo/{bar}')
는 /foo
의 모든 하위 경로와 매칭됩니다. 함수의 EventContext.params
객체에서 이 와일드 카드 경로 구성요소의 값을 확인할 수 있습니다. 이 예시에서는 값이 context.params.bar
로 제공됩니다.
와일드 카드가 포함된 경로는 단일 쓰기 작업의 여러 이벤트와 일치할 수 있습니다. 다음을 삽입하면
{
"foo": {
"hello": "world",
"firebase": "functions"
}
}
"hello": "world"
와 "firebase": "functions"
에 대해 각각 한 번씩 "/foo/{bar}"
경로와 두 번 일치하게 됩니다.
이벤트 데이터 처리
Realtime Database 이벤트를 처리할 때 반환되는 데이터 객체는 DataSnapshot
입니다.
onWrite
또는 onUpdate
이벤트의 경우 첫 번째 파라미터는 각각 트리거 이벤트 전후의 데이터 상태를 나타내는 스냅샷 2개가 포함된 Change
객체입니다. onCreate
와 onDelete
이벤트의 경우 반환되는 데이터 객체는 생성하거나 삭제한 데이터의 스냅샷입니다.
이 예시에서 함수는 지정된 경로의 스냅샷을 가져와 해당 위치의 문자열을 대문자로 변환하고 수정된 문자열을 데이터베이스에 씁니다.
// Listens for new messages added to /messages/:pushId/original and creates an // uppercase version of the message to /messages/:pushId/uppercase exports.makeUppercase = functions.database.ref('/messages/{pushId}/original') .onCreate((snapshot, context) => { // Grab the current value of what was written to the Realtime Database. const original = snapshot.val(); functions.logger.log('Uppercasing', context.params.pushId, original); const uppercase = original.toUpperCase(); // You must return a Promise when performing asynchronous tasks inside a Functions such as // writing to the Firebase Realtime Database. // Setting an "uppercase" sibling in the Realtime Database returns a Promise. return snapshot.ref.parent.child('uppercase').set(uppercase); });
사용자 인증 정보 액세스
EventContext.auth
및 EventContext.authType
에서 권한을 포함하여 함수를 트리거한 사용자의 정보에 액세스할 수 있습니다. 이 방법을 사용하면 사용자의 권한 수준에 따라 함수에서 다른 작업을 완료할 수 있으므로 보안 규칙을 적용하는 데 유용합니다.
const functions = require('firebase-functions/v1');
const admin = require('firebase-admin');
exports.simpleDbFunction = functions.database.ref('/path')
.onCreate((snap, context) => {
if (context.authType === 'ADMIN') {
// do something
} else if (context.authType === 'USER') {
console.log(snap.val(), 'written by', context.auth.uid);
}
});
또한 사용자 인증 정보를 활용하여 사용자로 '가장'하고 사용자 대신 쓰기 작업을 수행할 수 있습니다. 동시 실행 문제를 방지하기 위해 아래에 나와 있는 것처럼 앱 인스턴스를 삭제해야 합니다.
exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
.onCreate((snap, context) => {
const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
appOptions.databaseAuthVariableOverride = context.auth;
const app = admin.initializeApp(appOptions, 'app');
const uppercase = snap.val().toUpperCase();
const ref = snap.ref.parent.child('uppercase');
const deleteApp = () => app.delete().catch(() => null);
return app.database().ref(ref).set(uppercase).then(res => {
// Deleting the app is necessary for preventing concurrency leaks
return deleteApp().then(() => res);
}).catch(err => {
return deleteApp().then(() => Promise.reject(err));
});
});
이전 값 읽기
Change
객체의 before
속성을 사용하면 이벤트가 발생하기 전에 Realtime Database에 저장된 데이터를 검사할 수 있습니다. before
속성은 DataSnapshot
을 반환합니다. 여기서 모든 메서드(예: val()
과 exists()
)는 이전 값을 참조합니다. 다시 새 값을 읽으려면 원본 DataSnapshot
을 사용하거나 after
속성을 읽으면 됩니다. Change
에 있는 이 속성은 이벤트가 발생한 후의 데이터 상태를 나타내는 또 다른 DataSnapshot
입니다.
예를 들어 before
속성을 사용하여 데이터를 처음 만들 때만 함수가 텍스트를 대문자로 변환하도록 할 수 있습니다.
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const original = change.after.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return change.after.ref.parent.child('uppercase').set(uppercase);
});