Realtime Database ট্রিগার (ফার্স্ট জেনারেশন)

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

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

  1. নির্দিষ্ট Realtime Database লোকেশনে পরিবর্তন হওয়ার জন্য অপেক্ষা করে।
  2. কোনও ইভেন্ট ঘটলে ট্রিগার করে এবং এর টাস্কগুলি পারফর্ম করে (আমি Cloud Functions-এর সাথে কী করতে পারি? দেখুন) ব্যবহারের উদাহরণ দেখুন)।
  3. একটি ডেটা অবজেক্ট গ্রহণ করে যাতে নির্দিষ্ট ডকুমেন্টে স্টোর করা ডেটার একটি স্ন্যাপশট থাকে।

Realtime Database ফাংশন ট্রিগার করুন

functions.database-এর মাধ্যমে Realtime Database ইভেন্টের জন্য নতুন ফাংশন তৈরি করুন। ফাংশন কখন ট্রিগার হবে তা নিয়ন্ত্রণ করতে, ইভেন্ট হ্যান্ডলারগুলির মধ্যে একটি নির্দিষ্ট করুন এবং Realtime Database পাথ নির্দিষ্ট করুন যেখানে এটি ইভেন্টের জন্য শুনবে।

ইভেন্ট হ্যান্ডলার সেট করুন

ফাংশন আপনাকে নির্দিষ্টতার দুটি স্তরে Realtime Database ইভেন্টগুলি পরিচালনা করতে দেয়; আপনি বিশেষভাবে শুধুমাত্র তৈরি, আপডেট, বা মুছে ফেলার ইভেন্টগুলি শুনতে পারেন, অথবা আপনি কোনও পাথে যেকোনও ধরনের পরিবর্তন শুনতে পারেন। 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

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

আপনি কার্লি ব্র্যাকেট দিয়ে ঘিরে ওয়াইল্ডকার্ড হিসেবে পাথ কম্পোনেন্ট নির্দিষ্ট করতে পারেন; ref('foo/{bar}'), /foo-এর যেকোনও চাইল্ডের সাথে ম্যাচ করে। এইসব ওয়াইল্ডকার্ড পাথ কম্পোনেন্টের ভ্যালু আপনার ফাংশনের EventContext.params অবজেক্টের মধ্যে উপলভ্য। এই উদাহরণে, ভ্যালু context.params.bar হিসেবে উপলভ্য।

ওয়াইল্ডকার্ড সহ পাথ একটি রাইট থেকে একাধিক ইভেন্ট ম্যাচ করতে পারে। এর একটি ইনসার্ট

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

"/foo/{bar}" পাথ দুবার মিলে যায়: একবার "hello": "world"-এর সাথে এবং আবার "firebase": "functions"-এর সাথে।

ইভেন্ট ডেটা ম্যানেজ করা

Realtime Database ইভেন্ট ম্যানেজ করার সময়, যে ডেটা অবজেক্ট রিটার্ন করা হয় সেটি হল DataSnapshot। onWrite বা onUpdate ইভেন্টের জন্য, প্রথম প্যারামিটার হল একটি 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);
    });