Firebase 실시간 데이터베이스 트리거

Cloud Functions를 사용하면 함수와 동일한 Google Cloud 프로젝트에서 Firebase 실시간 데이터베이스의 이벤트를 처리할 수 있습니다. Cloud Functions를 사용하면 전체 관리 권한으로 데이터베이스 작업을 실행할 수 있으며 데이터베이스의 각 변경사항이 개별적으로 처리됩니다. Firebase Admin SDK를 통해 Firebase 실시간 데이터베이스를 변경할 수 있습니다.

일반적인 처리 과정에서 Firebase 실시간 데이터베이스 함수는 다음을 수행합니다.

  1. 특정 데이터베이스 위치가 변경되기를 기다립니다.

  2. 이벤트가 발생하면 트리거되어 작업을 수행합니다.

  3. 지정된 문서에 저장된 데이터 스냅샷을 포함하는 데이터 객체를 수신합니다.

이벤트 유형

함수를 사용하면 데이터베이스 이벤트를 처리할 수 있으며, 얼마나 세부적으로 처리할 지에 따라 두 가지 범위로 나눠집니다. 특히 생성, 업데이트 또는 삭제 이벤트만 수신 대기하거나 모든 유형의 경로 변경사항을 수신 대기할 수 있습니다. Cloud Functions가 실시간 데이터베이스에 지원하는 이벤트 유형은 다음과 같습니다.

이벤트 유형 트리거
providers/google.firebase.database/eventTypes/ref.write 실시간 데이터베이스에서 데이터가 생성, 업데이트 또는 삭제될 때 변형 이벤트에서 트리거됩니다.
providers/google.firebase.database/eventTypes/ref.create(기본) 실시간 데이터베이스에 새 데이터가 생성되면 트리거됩니다.
providers/google.firebase.database/eventTypes/ref.update 실시간 데이터베이스에서 데이터가 업데이트되면 트리거됩니다.
providers/google.firebase.database/eventTypes/ref.delete 실시간 데이터베이스에서 데이터가 삭제되면 트리거됩니다.

데이터베이스 경로 및 인스턴스 지정

함수가 트리거되는 시점과 위치를 제어하려면 경로를 지정하고 선택사항으로 데이터베이스 인스턴스를 지정해야 합니다.

경로

경로를 지정하면 해당 경로와 모든 하위 경로에 영향을 주는 모든 쓰기 이벤트와 일치됩니다. 함수의 경로를 /foo/bar로 설정하면 다음 두 위치 모두의 이벤트와 일치합니다.

 /foo/bar
 /foo/bar/baz/really/deep/path

두 경우 모두 Firebase에서는 /foo/bar에서 이벤트가 발생한 것으로 해석하고, 이벤트 데이터에는 /foo/bar의 이전 데이터와 새 데이터가 포함됩니다. 이벤트 데이터가 커질 가능성이 있다면 데이터베이스 루트 근처에 단일 함수를 사용하는 대신 더 깊은 경로에 여러 함수를 사용하는 것이 좋습니다. 성능을 최적화하려면 최대한 깊은 수준의 데이터만 요청합니다.

경로 구성요소를 중괄호로 묶어 와일드 카드로 지정할 수 있습니다. foo/{bar}/foo의 모든 하위 경로와 일치합니다. 함수의 event.params 객체에서 이 와일드 카드 경로 구성요소의 값을 확인할 수 있습니다. 이 예시에서는 값이 event.params.bar로 제공됩니다.

와일드 카드가 포함된 경로는 단일 쓰기 작업의 여러 이벤트와 일치할 수 있습니다. 다음을 삽입합니다.

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

"hello": "world", "firebase": "functions"에 대해 한 번씩 /foo/{bar} 경로와 두 번 일치하게 됩니다.

인스턴스

Google Cloud Console을 사용할 때 데이터베이스 인스턴스를 지정해야 합니다.

Google Cloud CLI를 사용할 때는 --trigger-resource 문자열의 일부로 인스턴스를 지정해야 합니다.

예를 들어 다음은 --trigger-resource 문자열에 다음을 사용합니다.

--trigger-resource projects/_/instances/DATABASE_INSTANCE/refs/PATH

이벤트 구조

실시간 데이터베이스 이벤트 처리 시 data 객체에는 JSON 객체 형식으로 제공되는 두 개의 속성이 포함됩니다.

  • data: 함수를 트리거한 이벤트 발생 이전의 데이터 스냅샷입니다.

  • delta: 함수를 트리거한 이벤트 발생 이후의 데이터의 스냅샷입니다.

코드 샘플

Node.js

/**
 * Background Function triggered by a change to a Firebase RTDB reference.
 *
 * @param {!Object} event The Cloud Functions event.
 * @param {!Object} context The Cloud Functions event context.
 */
exports.helloRTDB = (event, context) => {
  const triggerResource = context.resource;

  console.log(`Function triggered by change to: ${triggerResource}`);
  console.log(`Admin?: ${!!context.auth.admin}`);
  console.log('Delta:');
  console.log(JSON.stringify(event.delta, null, 2));
};

Python

import json

def hello_rtdb(data, context):
    """Triggered by a change to a Firebase RTDB reference.
    Args:
        data (dict): The event payload.
        context (google.cloud.functions.Context): Metadata for the event.
    """
    trigger_resource = context.resource

    print("Function triggered by change to: %s" % trigger_resource)
    print("Admin?: %s" % data.get("admin", False))
    print("Delta:")
    print(json.dumps(data["delta"]))

Go


// Package p contains a Cloud Function triggered by a Firebase Realtime Database
// event.
package p

import (
	"context"
	"fmt"
	"log"

	"cloud.google.com/go/functions/metadata"
)

// RTDBEvent is the payload of a RTDB event.
type RTDBEvent struct {
	Data  interface{} `json:"data"`
	Delta interface{} `json:"delta"`
}

// HelloRTDB handles changes to a Firebase RTDB.
func HelloRTDB(ctx context.Context, e RTDBEvent) error {
	meta, err := metadata.FromContext(ctx)
	if err != nil {
		return fmt.Errorf("metadata.FromContext: %w", err)
	}
	log.Printf("Function triggered by change to: %v", meta.Resource)
	log.Printf("%+v", e)
	return nil
}

Java

import com.google.cloud.functions.Context;
import com.google.cloud.functions.RawBackgroundFunction;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.logging.Logger;

public class FirebaseRtdb implements RawBackgroundFunction {
  private static final Logger logger = Logger.getLogger(FirebaseRtdb.class.getName());

  // Use GSON (https://github.com/google/gson) to parse JSON content.
  private static final Gson gson = new Gson();

  @Override
  public void accept(String json, Context context) {
    logger.info("Function triggered by change to: " + context.resource());

    JsonObject body = gson.fromJson(json, JsonObject.class);

    boolean isAdmin = false;
    if (body != null && body.has("auth")) {
      JsonObject authObj = body.getAsJsonObject("auth");
      isAdmin = authObj.has("admin") && authObj.get("admin").getAsBoolean();
    }

    logger.info("Admin?: " + isAdmin);

    if (body != null && body.has("delta")) {
      logger.info("Delta:");
      logger.info(body.get("delta").toString());
    }
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Firebase.Database.V1;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace FirebaseRtdb;

public class Function : ICloudEventFunction<ReferenceEventData>
{
    private readonly ILogger _logger;

    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public Task HandleAsync(CloudEvent cloudEvent, ReferenceEventData data, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Function triggered by change to {subject}", cloudEvent.Subject);
        _logger.LogInformation("Delta: {delta}", data.Delta);

        // In this example, we don't need to perform any asynchronous operations, so the
        // method doesn't need to be declared async.
        return Task.CompletedTask;
    }
}

Ruby

require "functions_framework"

# Triggered by a change to a Firebase RTDB document.
FunctionsFramework.cloud_event "hello_rtdb" do |event|
  # Event-triggered Ruby functions receive a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  # The Firebase event payload can be obtained from the `data` field.
  payload = event.data

  logger.info "Function triggered by change to: #{event.source}"
  logger.info "Admin?: #{payload.fetch 'admin', false}"
  logger.info "Delta: #{payload['delta']}"
end

PHP


use Google\CloudFunctions\CloudEvent;

function firebaseRTDB(CloudEvent $cloudevent)
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');

    fwrite($log, 'Event: ' . $cloudevent->getId() . PHP_EOL);

    $data = $cloudevent->getData();
    $resource = $data['resource'] ?? '<null>';

    fwrite($log, 'Function triggered by change to: ' . $resource . PHP_EOL);

    $isAdmin = isset($data['auth']['admin']) && $data['auth']['admin'] == true;

    fwrite($log, 'Admin?: ' . var_export($isAdmin, true) . PHP_EOL);
    fwrite($log, 'Delta: ' . json_encode($data['delta'] ?? '') . PHP_EOL);
}

함수 배포

다음 gcloud 명령어는 경로 /messages/{pushId}/originalcreate 이벤트에 의해 트리거되는 함수를 배포합니다.

gcloud functions deploy FUNCTION_NAME \
  --entry-point ENTRY_POINT \
  --trigger-event providers/google.firebase.database/eventTypes/ref.create \
  --trigger-resource projects/_/instances/DATABASE_INSTANCE/refs/messages/{pushId}/original \
  --runtime RUNTIME
인수 설명
FUNCTION_NAME 배포중인 Cloud 함수의 등록된 이름입니다. 소스 코드의 함수 이름 또는 임의의 문자열일 수 있습니다. FUNCTION_NAME이 임의의 문자열이면 --entry-point 플래그를 포함해야 합니다.
--entry-point ENTRY_POINT 소스 코드의 함수 또는 클래스 이름입니다. FUNCTION_NAME을 사용하여 배포 중에 실행할 소스 코드에 함수를 지정하지 않은 경우 선택사항입니다. 이러한 경우 --entry-point를 사용하여 실행 가능한 함수의 이름을 제공해야 합니다.
--trigger-event NAME 함수가 수신할 이벤트 유형 이름입니다. 이 경우, 쓰기, 생성, 업데이트 또는 삭제 중 하나가 됩니다.
--trigger-resource NAME 함수가 리슨할 정규화된 데이터베이스 경로입니다. projects/_/instances/DATABASE_INSTANCE/refs/PATH 형식을 준수해야 합니다.
--runtime RUNTIME 사용 중인 런타임 이름입니다. 전체 목록은 gcloud 참조에서 확인할 수 있습니다.