রিয়েলটাইম ডাটাবেস ট্রিগার

Cloud Functions ব্যবহার করে, আপনি ক্লায়েন্ট কোড আপডেট করার প্রয়োজন ছাড়াই Firebase Realtime Database ইভেন্টগুলো পরিচালনা করতে পারেন। Cloud Functions আপনাকে সম্পূর্ণ প্রশাসনিক অধিকারসহ Realtime Database অপারেশন চালানোর সুযোগ দেয় এবং এটি নিশ্চিত করে যে Realtime Database প্রতিটি পরিবর্তন স্বতন্ত্রভাবে প্রক্রিয়া করা হয়। আপনি ডেটা স্ন্যাপশট অথবা অ্যাডমিন এসডিকে-এর মাধ্যমে Firebase Realtime Database পরিবর্তন আনতে পারেন।

একটি সাধারণ জীবনচক্রে, একটি Firebase Realtime Database ফাংশন নিম্নলিখিত কাজগুলো করে থাকে:

  1. একটি নির্দিষ্ট Realtime Database পাথের পরিবর্তনের জন্য অপেক্ষা করে।
  2. কোনো ঘটনা ঘটলে এটি সক্রিয় হয় এবং এর কাজগুলো সম্পাদন করে।
  3. একটি ডেটা অবজেক্ট গ্রহণ করে, যাতে ওই পাথে সংরক্ষিত ডেটার একটি স্ন্যাপশট থাকে।

Firebase Realtime Database ডাটাবেস নোড লেখা, তৈরি করা, আপডেট করা বা মুছে ফেলার প্রতিক্রিয়ায় আপনি একটি ফাংশন ট্রিগার করতে পারেন। ফাংশনটি কখন ট্রিগার হবে তা নিয়ন্ত্রণ করতে, ইভেন্ট হ্যান্ডলারগুলোর মধ্যে একটি নির্দিষ্ট করুন এবং Realtime Database সেই পাথটি উল্লেখ করুন যেখানে এটি ইভেন্টের জন্য অপেক্ষা করবে।

ফাংশনের অবস্থান নির্ধারণ করা

একটি Realtime Database ইনস্ট্যান্সের অবস্থান এবং ফাংশনের অবস্থানের মধ্যে দূরত্বের কারণে উল্লেখযোগ্য নেটওয়ার্ক ল্যাটেন্সি তৈরি হতে পারে। এছাড়াও, অঞ্চলের মধ্যে অমিলের ফলে ডেপ্লয়মেন্ট ব্যর্থ হতে পারে। এই পরিস্থিতিগুলো এড়াতে, ফাংশনের অবস্থান এমনভাবে নির্দিষ্ট করুন যাতে তা ডাটাবেস ইনস্ট্যান্সের অবস্থানের সাথে মিলে যায়।

Realtime Database ইভেন্ট পরিচালনা করা

ফাংশনগুলো আপনাকে দুটি নির্দিষ্ট স্তরে Realtime Database ইভেন্ট পরিচালনা করার সুযোগ দেয়; আপনি বিশেষভাবে শুধুমাত্র রাইট, ক্রিয়েশন, আপডেট বা ডিলিশন ইভেন্টগুলো শুনতে পারেন, অথবা কোনো রেফারেন্সের যেকোনো ধরনের পরিবর্তনও শুনতে পারেন।

Realtime Database ইভেন্টগুলিতে সাড়া দেওয়ার জন্য এই হ্যান্ডলারগুলি উপলব্ধ আছে:

নোড.জেএস

  • Realtime Database ডেটা তৈরি, আপডেট বা মুছে ফেলা হলে onValueWritten() ট্রিগার হয়।
  • onValueCreated() শুধুমাত্র Realtime Database ডেটা তৈরি হলে ট্রিগার হয়।
  • onValueUpdated() শুধুমাত্র তখনই ট্রিগার হয় যখন Realtime Database ডেটা আপডেট করা হয়।
  • onValueDeleted() শুধুমাত্র Realtime Database ডেটা মুছে ফেলার সময় ট্রিগার হয়।

পাইথন

  • Realtime Database ডেটা তৈরি, আপডেট বা মুছে ফেলা হলে on_value_written() ফাংশনটি ট্রিগার হয়।
  • on_value_created() শুধুমাত্র Realtime Database ডেটা তৈরি হলে ট্রিগার হয়।
  • on_value_updated() শুধুমাত্র তখনই ট্রিগার হয় যখন Realtime Database ডেটা আপডেট করা হয়।
  • on_value_deleted() শুধুমাত্র Realtime Database ডেটা মুছে ফেলার সময় ট্রিগার হয়।

প্রয়োজনীয় মডিউলগুলো আমদানি করুন

আপনার ফাংশন সোর্সে, আপনি যে SDK মডিউলগুলো ব্যবহার করতে চান, সেগুলো অবশ্যই ইম্পোর্ট করতে হবে। এই স্যাম্পলটির জন্য, HTTP এবং Realtime Database মডিউলের পাশাপাশি Realtime Database এ লেখার জন্য Firebase Admin SDK মডিউলটিও ইম্পোর্ট করা আবশ্যক।

নোড.জেএস

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/https");
const {onValueCreated} = require("firebase-functions/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();

পাইথন

# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn

# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db

app = initialize_app()

ইনস্ট্যান্স এবং পাথ নির্দিষ্ট করুন

আপনার ফাংশনটি কখন এবং কোথায় ট্রিগার হবে তা নিয়ন্ত্রণ করতে, একটি পাথ এবং ঐচ্ছিকভাবে একটি Realtime Database ইনস্ট্যান্স দিয়ে আপনার ফাংশনটি কনফিগার করুন। আপনি যদি কোনো ইনস্ট্যান্স নির্দিষ্ট না করেন, তাহলে ফাংশনটি ফাংশন রিজিয়নের সমস্ত Realtime Database ইনস্ট্যান্সে সাড়া দেবে। আপনি একই রিজিয়নের ইনস্ট্যান্সগুলোর একটি নির্দিষ্ট উপসেটে ডেপ্লয় করার জন্য একটি Realtime Database ইনস্ট্যান্স প্যাটার্নও নির্দিষ্ট করতে পারেন।

উদাহরণস্বরূপ:

নোড.জেএস

// All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
// There must be at least one Realtime Database present in us-central1.
const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => {
  // …
});

// Instance named "my-app-db-2", at path "/user/{uid}".
// The "my-app-db-2" instance must exist in this region.
const OnWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid}",
    instance: "my-app-db-2"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);

// Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
// There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
const onWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid=*@gmail.com}",
    instance: "my-app-db-*"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);

পাইথন

# All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
# There must be at least one Realtime Database present in us-central1.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid}",
    instance="my-app-db-2",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid=*@gmail.com}",
    instance="my-app-db-*",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

এই প্যারামিটারগুলো আপনার ফাংশনকে Realtime Database ইনস্ট্যান্সের মধ্যে একটি নির্দিষ্ট পাথে রাইট অপারেশন পরিচালনা করতে নির্দেশ দেয়।

পাথ স্পেসিফিকেশন সেই পাথের সাথে সম্পর্কিত সমস্ত রাইট অপারেশনের সাথে মেলে, যার মধ্যে এর নিচের যেকোনো স্থানে ঘটা রাইটও অন্তর্ভুক্ত। যদি আপনি আপনার ফাংশনের জন্য পাথটি /foo/bar হিসেবে সেট করেন, তাহলে এটি এই উভয় স্থানের ইভেন্টগুলোর সাথেই মিলবে:

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

উভয় ক্ষেত্রেই, Firebase ধরে নেয় যে ইভেন্টটি /foo/bar এ ঘটেছে, এবং ইভেন্টের ডেটাতে /foo/bar এর পুরোনো ও নতুন ডেটা অন্তর্ভুক্ত থাকে। যদি ইভেন্টের ডেটার পরিমাণ বেশি হওয়ার সম্ভাবনা থাকে, তবে আপনার ডাটাবেসের রুটের কাছাকাছি একটিমাত্র ফাংশন ব্যবহার না করে, আরও গভীর পাথে একাধিক ফাংশন ব্যবহার করার কথা বিবেচনা করুন। সর্বোত্তম পারফরম্যান্সের জন্য, কেবলমাত্র সম্ভাব্য গভীরতম স্তর থেকেই ডেটার জন্য অনুরোধ করুন।

ওয়াইল্ডকার্ডিং এবং ক্যাপচারিং

ক্যাপচার করার জন্য আপনি {key} , {key=*} , {key=prefix*} , {key=*suffix} ব্যবহার করতে পারেন। একক-সেগমেন্ট ওয়াইল্ডকার্ডিংয়ের জন্য * , prefix* , *suffix ব্যবহৃত হয়। দ্রষ্টব্য: ** একাধিক-সেগমেন্ট ওয়াইল্ডকার্ডিং বোঝায়, যা Realtime Database সমর্থন করে না। পাথ প্যাটার্ন বুঝুন দেখুন।

পাথ ওয়াইল্ডকার্ডিং। আপনি একটি পাথ কম্পোনেন্টকে ওয়াইল্ডকার্ড হিসেবে নির্দিষ্ট করতে পারেন:

  • অ্যাস্টারিস্ক ( * ) ব্যবহার করুন। উদাহরণস্বরূপ, foo/* নোড হায়ারার্কিতে foo/ এর এক স্তর নিচের যেকোনো চাইল্ডকে ম্যাচ করে।
  • শুধুমাত্র অ্যাস্টারিস্ক ( * ) সম্বলিত একটি সেগমেন্ট ব্যবহার করে। উদাহরণস্বরূপ, foo/app*-us foo/ নিচের যেকোনো চাইল্ড সেগমেন্টকে ম্যাচ করে, যেগুলোর app প্রিফিক্স এবং -us সাফিক্স রয়েছে।

ওয়াইল্ডকার্ডযুক্ত পাথগুলো, উদাহরণস্বরূপ, একটিমাত্র রাইট থেকে একাধিক ইভেন্ট মেলাতে পারে। একটি ইনসার্ট

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

পাথ "/foo/*" দুইবার ম্যাচ করছে: একবার "hello": "world" এবং আবার "firebase": "functions" দিয়ে।

পাথ ক্যাপচারিং। আপনি আপনার ফাংশন কোডে ব্যবহারের জন্য পাথ ম্যাচগুলোকে নামযুক্ত ভেরিয়েবলে ক্যাপচার করতে পারেন (যেমন /user/{uid} , /user/{uid=*-us} )।

ক্যাপচার ভেরিয়েবলগুলোর মান আপনার ফাংশনের database.DatabaseEvent.params অবজেক্টের মধ্যে পাওয়া যায়।

ইনস্ট্যান্স ওয়াইল্ডকার্ডিং। আপনি ওয়াইল্ডকার্ডিং ব্যবহার করে একটি ইনস্ট্যান্স কম্পোনেন্টও নির্দিষ্ট করতে পারেন। একটি ইনস্ট্যান্স ওয়াইল্ডকার্ডে প্রিফিক্স, সাফিক্স বা উভয়ই থাকতে পারে (যেমন my-app-*-prod )।

ওয়াইল্ডকার্ড এবং ক্যাপচার রেফারেন্স

Cloud Functions (২য় প্রজন্ম) এবং Realtime Database ক্ষেত্রে, ref এবং instance নির্দিষ্ট করার সময় একটি প্যাটার্ন ব্যবহার করা যায়। প্রতিটি ট্রিগার ইন্টারফেসে একটি ফাংশনের স্কোপ নির্ধারণের জন্য নিম্নলিখিত অপশনগুলো থাকবে:

ref নির্দিষ্ট করা instance নির্দিষ্ট করা আচরণ
একক ( /foo/bar ) নির্দিষ্ট না করা ফাংশন অঞ্চলের সমস্ত ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।
একক ( /foo/bar ) একক ( 'my-new-db' ) ফাংশন অঞ্চলের মধ্যে নির্দিষ্ট ইনস্ট্যান্সের স্কোপ হ্যান্ডলার।
একক ( /foo/bar ) প্যাটার্ন ( 'inst-prefix*' ) ফাংশন অঞ্চলের মধ্যে প্যাটার্নের সাথে মেলে এমন সমস্ত ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।
প্যাটার্ন ( /foo/{bar} ) নির্দিষ্ট না করা ফাংশন অঞ্চলের সমস্ত ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।
প্যাটার্ন ( /foo/{bar} ) একক ( 'my-new-db' ) ফাংশন অঞ্চলের মধ্যে নির্দিষ্ট ইনস্ট্যান্সের স্কোপ হ্যান্ডলার।
প্যাটার্ন ( /foo/{bar} ) প্যাটার্ন ( 'inst-prefix*' ) ফাংশন অঞ্চলের মধ্যে প্যাটার্নের সাথে মেলে এমন সমস্ত ইনস্ট্যান্সের জন্য স্কোপ হ্যান্ডলার।

ইভেন্ট ডেটা পরিচালনা করুন

যখন একটি Realtime Database ইভেন্ট ট্রিগার হয়, তখন এটি আপনার হ্যান্ডলার ফাংশনে একটি Event অবজেক্ট পাঠায়। এই অবজেক্টটির একটি data প্রপার্টি থাকে, যা ক্রিয়েশন এবং ডিলিশন ইভেন্টের ক্ষেত্রে, তৈরি বা মুছে ফেলা ডেটার একটি স্ন্যাপশট ধারণ করে।

এই উদাহরণে, ফাংশনটি উল্লেখিত পাথের ডেটা পুনরুদ্ধার করে, সেই স্থানের স্ট্রিংটিকে আপারকেসে রূপান্তর করে এবং সেই পরিবর্তিত স্ট্রিংটি ডাটাবেসে লিখে দেয়:

নোড.জেএস

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
// for all databases in 'us-central1'
exports.makeuppercase = onValueCreated(
    "/messages/{pushId}/original",
    (event) => {
    // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      logger.log("Uppercasing", event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing
      // asynchronous tasks inside a function, such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the
      // Realtime Database returns a Promise.
      return event.data.ref.parent.child("uppercase").set(uppercase);
    },
);

পাইথন

@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Grab the value that was written to the Realtime Database.
    original = event.data
    if not isinstance(original, str):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

পূর্ববর্তী মান পড়া হচ্ছে

write বা update ইভেন্টের ক্ষেত্রে, data প্রপার্টিটি একটি Change অবজেক্ট, যাতে দুটি স্ন্যাপশট থাকে যা ট্রিগারিং ইভেন্টের আগে ও পরের ডেটার অবস্থা তুলে ধরে। Change অবজেক্টটির একটি before প্রপার্টি থাকে, যা আপনাকে ইভেন্টের আগে Realtime Database কী সেভ করা হয়েছিল তা পরীক্ষা করতে দেয় এবং একটি after প্রপার্টি থাকে, যা ইভেন্টটি ঘটার পরে ডেটার অবস্থা তুলে ধরে।

উদাহরণস্বরূপ, ফাংশনটি যাতে শুধুমাত্র প্রথমবার তৈরি করার সময় টেক্সটকে আপারকেস করে, তা নিশ্চিত করতে before প্রপার্টিটি ব্যবহার করা যেতে পারে:

নোড.জেএস

  exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => {
        // Only edit data when it is first created.
        if (event.data.before.exists()) {
          return null;
        }
        // Exit when the data is deleted.
        if (!event.data.after.exists()) {
          return null;
        }
        // Grab the current value of what was written to the Realtime Database.
        const original = event.data.after.val();
        console.log('Uppercasing', event.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 event.data.after.ref.parent.child('uppercase').set(uppercase);
      });

পাইথন

@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Only edit data when it is first created.
    if event.data.before is not None:
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value that was written to the Realtime Database.
    original = event.data.after
    if not hasattr(original, "upper"):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

প্রমাণীকরণ প্রসঙ্গ অ্যাক্সেস করা

RTDB ইভেন্টআর্ক ইভেন্ট দ্বারা ট্রিগার হওয়া ফাংশনগুলির ক্ষেত্রে, প্রমাণীকরণ প্রসঙ্গটি ইভেন্ট পেলোডে অন্তর্ভুক্ত থাকে:

  • authtype : যে প্রিন্সিপাল ইভেন্টটি ট্রিগার করেছে তার ধরন। সম্ভাব্য মানগুলো হলো:
    • app_user : ডেভেলপারের অ্যাপ্লিকেশনের একজন শেষ ব্যবহারকারী।
    • admin : একটি পরিষেবা অ্যাকাউন্ট।
    • unauthenticated : একজন অপ্রমাণিত ব্যবহারকারী।
    • unknown : প্রমাণীকরণ তথ্য অনুপলব্ধ থাকলে এটিই ডিফল্ট।
  • authid : প্রিন্সিপালের অনন্য শনাক্তকারী।
    • যদি authtype app_user হয়, তাহলে এটি হলো ব্যবহারকারীর UID।
    • যদি authtype admin হয়, তাহলে এটি হলো সার্ভিস অ্যাকাউন্ট বা IAM ব্যবহারকারীর ইমেল।

এই কোডটি শুধুমাত্র তখনই বার্তার টেক্সটকে আপারকেসে রূপান্তর করে, যখন ফাংশনটি চালু করা ব্যবহারকারী একজন অ্যাডমিন না হন। এছাড়াও, কোডটি এটাও যাচাই করে দেখে যে, বার্তাটি চালু করা ব্যবহারকারীই এর প্রকৃত প্রেরক কি না।

নোড.জেএস

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onValueWritten} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");
const admin = require("firebase-admin");

admin.initializeApp();

exports.dbtrigger = onValueWritten("/messages/{pushId}/original", async (event) => {
  // 1. Check whether authtype is admin. If it is, skip this operation.
  if (event.authType === "admin") {
    logger.log("Modification by admin detected. Skipping uppercase conversion.");
    return null;
  }

  // 2. Retrieve the userID of the sender (assumed sibling node 'senderId')
  const snapshot = await event.data.after.ref.parent.child("senderId").get();
  const senderId = snapshot.val();

  // 3. Check if userID of sender of message = event.authid
  if (senderId !== event.authId) {
    logger.error(`Unauthorized write: senderId (${senderId}) does not match authId (${event.authId})`);
    return null;
  }

  // Grab the value that was written to the Realtime Database.
  const original = event.data.after.val();
  logger.log("Uppercasing", event.params.pushId, original);
  const uppercase = original.toUpperCase();

  // Return the promise to set the "uppercase" sibling node.
  return event.data.after.ref.parent.child("uppercase").set(uppercase);
});

পাইথন

from firebase_functions import db_fn
from firebase_admin import initialize_app, db

initialize_app()

@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[db_fn.Change]) -> None:
    # 1. Check whether authtype is admin. If it is, skip this operation.
    if event.auth_type == "admin":
        print("Admin user detected. Skipping.")
        return

    # 2. Retrieve the userID of the sender (assumed sibling node: 'senderId')
    parent_ref = db.reference(event.reference).parent
    sender_id = parent_ref.child("senderId").get()

    # 3. Check if userID of sender = event.auth_id
    if sender_id != event.auth_id:
        print(f"Unauthorized: sender_id {sender_id} != auth_id {event.auth_id}")
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value and uppercase it
    original = event.data.after
    if not isinstance(original, str):
        return

    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent_ref.child("uppercase").set(upper)