טריגרים של מסד נתונים בזמן אמת ב-Firebase

באמצעות פונקציות Cloud Run, אתם יכולים לטפל באירועים במסד נתונים בזמן אמת ב-Firebase באותו פרויקט Google Cloud שבו נמצאת הפונקציה. פונקציות Cloud Run מאפשרות להריץ פעולות במסד נתונים עם הרשאות אדמין מלאות, ומוודאות שכל שינוי במסד הנתונים יעבור עיבוד בנפרד. אפשר לבצע שינויים במסד נתונים בזמן אמת ב-Firebase באמצעות SDK של Firebase לאדמינים.

במחזור חיים טיפוסי, פונקציה של מסד נתונים בזמן אמת ב-Firebase מבצעת את הפעולות הבאות:

  1. הפונקציה ממתינה לשינויים במיקום מסוים במסד הנתונים.

  2. מופעל כשמתרחש אירוע ומבצע את המשימות שלו.

  3. מקבל אובייקט נתונים שמכיל תמונת מצב של הנתונים שמאוחסנים במסמך שצוין.

סוגי אירועים

פונקציות מאפשרות לכם לטפל באירועים במסד נתונים בשני רמות ספציפיות. אתם יכולים להאזין רק לאירועי יצירה, עדכון או מחיקה, או להאזין לכל שינוי שמתרחש בנתיב. פונקציות Cloud Run תומכות בסוגי האירועים הבאים של מסד נתונים בזמן אמת:

סוג האירוע הטריגר
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 מופעל כשנתונים נמחקים מ-Realtime Database.

ציון הנתיב והמופע של מסד הנתונים

כדי לשלוט מתי ואיפה הפונקציה תופעל, צריך לציין נתיב, ואפשר גם לציין מופע של מסד נתונים.

נתיב

הגדרות הנתיב תואמות לכל פעולות הכתיבה שמשפיעות על נתיב, כולל פעולות כתיבה שמתבצעות בכל מקום מתחתיו. אם מגדירים את הנתיב של הפונקציה כ-/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"
  }
}

תואם לנתיב /foo/{bar} פעמיים: פעם אחת עם "hello": "world" ופעם נוספת עם "firebase": "functions".

Instance

כשמשתמשים במסוף Google Cloud , צריך לציין את מופע מסד הנתונים.

כשמשתמשים ב-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 פורסת פונקציה שתופעל על ידי אירועים מסוג create בנתיב /messages/{pushId}/original:

gcloud functions deploy FUNCTION_NAME \
  --no-gen2 \
  --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 Run שפורסים. זה יכול להיות שם של פונקציה בקוד המקור, או מחרוזת שרירותית. אם FUNCTION_NAME היא מחרוזת שרירותית, צריך לכלול את הדגל --entry-point.
--entry-point ENTRY_POINT השם של פונקציה או מחלקה בקוד המקור. אופציונלי, אלא אם לא השתמשתם ב-FUNCTION_NAME כדי לציין את הפונקציה בקוד המקור שתופעל במהלך הפריסה. במקרה כזה, צריך להשתמש ב---entry-point כדי לציין את השם של הפונקציה שניתנת להרצה.
--trigger-event NAME השם של סוג האירוע שהפונקציה רוצה לקבל. במקרה כזה, הערך יהיה אחד מהערכים הבאים: write, ‏ create, ‏ update או delete.
--trigger-resource NAME הנתיב המלא למסד הנתונים שהפונקציה תאזין לו. הפורמט הנדרש הוא: projects/_/instances/DATABASE_INSTANCE/refs/PATH.
--runtime RUNTIME שם זמן הריצה שבו אתם משתמשים. רשימה מלאה זמינה בחומר העזר בנושא gcloud.