ใช้เงื่อนไขในกฎความปลอดภัยของฐานข้อมูลเรียลไทม์

คู่มือนี้สร้างขึ้นจากคู่มือเรียนรู้ภาษาหลักของกฎการรักษาความปลอดภัยของ Firebase เพื่อแสดงวิธีเพิ่มเงื่อนไขลงในกฎการรักษาความปลอดภัยของฐานข้อมูลเรียลไทม์ของ Firebase

องค์ประกอบหลักหลักของกฎความปลอดภัยของ Realtime Database คือเงื่อนไข ต คือนิพจน์บูลีนที่กำหนดว่าการดำเนินการหนึ่งๆ หรือไม่ ควรยอมรับหรือปฏิเสธ สำหรับกฎพื้นฐาน จะใช้ true และ false ลิเทอรัลเป็น สภาพปกติใช้งานได้ดี แต่ภาษาของกฎการรักษาความปลอดภัยของฐานข้อมูลเรียลไทม์ช่วยให้คุณ วิธีเขียนเงื่อนไขที่ซับซ้อนมากขึ้น ซึ่งมีดังนี้

  • ตรวจสอบการตรวจสอบสิทธิ์ผู้ใช้
  • ประเมินข้อมูลที่มีอยู่เทียบกับข้อมูลที่ส่งเข้ามาใหม่
  • เข้าถึงและเปรียบเทียบส่วนต่างๆ ของฐานข้อมูล
  • ตรวจสอบข้อมูลขาเข้า
  • ใช้โครงสร้างของการค้นหาขาเข้าสำหรับตรรกะการรักษาความปลอดภัย

การใช้ตัวแปร $ เพื่อบันทึกกลุ่มเส้นทาง

คุณสามารถบันทึกส่วนต่างๆ ของเส้นทางสำหรับการอ่านหรือเขียนได้โดยการประกาศ บันทึกตัวแปรด้วยคำนำหน้า $ การดำเนินการนี้เป็นไวลด์การ์ดและจัดเก็บมูลค่าของกุญแจดังกล่าวไว้ใช้ภายใน เงื่อนไขของกฎ:

{
  "rules": {
    "rooms": {
      // this rule applies to any child of /rooms/, the key for each room id
      // is stored inside $room_id variable for reference
      "$room_id": {
        "topic": {
          // the room's topic can be changed if the room id has "public" in it
          ".write": "$room_id.contains('public')"
        }
      }
    }
  }
}

คุณยังใช้ตัวแปร $ แบบไดนามิกควบคู่ไปกับเส้นทางคงที่ได้ด้วย ในตัวอย่างนี้ เราใช้ตัวแปร $other เพื่อประกาศ กฎ .validate ข้อที่ช่วยดูแลให้ widget ไม่มีบุตรหลานนอกจาก title และ color การเขียนที่จะทำให้มีการสร้างรายการย่อยเพิ่มเติมจะล้มเหลว

{
  "rules": {
    "widget": {
      // a widget can have a title or color attribute
      "title": { ".validate": true },
      "color": { ".validate": true },

      // but no other child paths are allowed
      // in this case, $other means any key excluding "title" and "color"
      "$other": { ".validate": false }
    }
  }
}

การตรวจสอบสิทธิ์

รูปแบบหนึ่งของกฎความปลอดภัยที่ใช้กันมากที่สุดคือการควบคุมการเข้าถึงตาม สถานะการตรวจสอบสิทธิ์ของผู้ใช้ เช่น แอปของคุณอาจต้องการอนุญาตเฉพาะ ผู้ใช้ที่ลงชื่อเข้าใช้เพื่อเขียนข้อมูล

หากแอปใช้การตรวจสอบสิทธิ์ Firebase ตัวแปร request.auth จะมี ข้อมูลการตรวจสอบสิทธิ์สำหรับไคลเอ็นต์ที่ขอข้อมูล สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ request.auth โปรดดูข้อมูลอ้างอิง เอกสารประกอบ

การตรวจสอบสิทธิ์ Firebase ผสานรวมกับฐานข้อมูลเรียลไทม์ของ Firebase เพื่อให้คุณควบคุมข้อมูลได้ ตามผู้ใช้แต่ละรายโดยใช้เงื่อนไข เมื่อผู้ใช้ตรวจสอบสิทธิ์ auth ในกฎการรักษาความปลอดภัยของฐานข้อมูลแบบเรียลไทม์จะได้รับการสร้างขึ้นด้วย ข้อมูลนี้รวมถึงตัวระบุที่ไม่ซ้ำกัน (uid) ข้อมูลบัญชีที่ลิงก์ เช่น รหัส Facebook หรืออีเมล และ ข้อมูลอื่นๆ หากใช้ผู้ให้บริการตรวจสอบสิทธิ์ที่กำหนดเอง คุณก็จะเพิ่มช่องของตนเองได้ ไปยังเพย์โหลดการตรวจสอบสิทธิ์ของผู้ใช้

ส่วนนี้จะอธิบายวิธีรวมภาษาของกฎการรักษาความปลอดภัยของฐานข้อมูลเรียลไทม์ของ Firebase กับ ข้อมูลการตรวจสอบสิทธิ์ของผู้ใช้ เมื่อรวม 2 แนวคิดเข้าด้วยกัน คุณสามารถควบคุมการเข้าถึงข้อมูลตามข้อมูลประจำตัวของผู้ใช้ได้

ตัวแปร auth

ตัวแปร auth ที่กำหนดไว้ล่วงหน้าในกฎเป็นค่าว่างก่อน มีการตรวจสอบสิทธิ์เกิดขึ้น

เมื่อตรวจสอบสิทธิ์ผู้ใช้ด้วยการตรวจสอบสิทธิ์ของ Firebase แล้ว จะประกอบด้วยแอตทริบิวต์ต่อไปนี้

ผู้ให้ทุน วิธีการตรวจสอบสิทธิ์ที่ใช้ ("password", "anonymous", "facebook", "github", "google", หรือ "twitter")
UID รหัสผู้ใช้ที่ไม่ซ้ำซึ่งไม่ซ้ำกันในผู้ให้บริการทั้งหมด
โทเค็น เนื้อหาของโทเค็นรหัสการตรวจสอบสิทธิ์ Firebase ดูข้อมูลอ้างอิง เอกสารประกอบสำหรับ auth.tokenเพื่อดูรายละเอียดเพิ่มเติม

ต่อไปนี้คือกฎตัวอย่างที่ใช้ตัวแปร auth เพื่อให้แน่ใจว่า ผู้ใช้แต่ละรายจะเขียนไปยังเส้นทางที่ผู้ใช้กำหนดได้เท่านั้น ดังนี้

{
  "rules": {
    "users": {
      "$user_id": {
        // grants write access to the owner of this user account
        // whose uid must exactly match the key ($user_id)
        ".write": "$user_id === auth.uid"
      }
    }
  }
}

การจัดโครงสร้างฐานข้อมูลให้รองรับเงื่อนไขการตรวจสอบสิทธิ์

การวางโครงสร้างฐานข้อมูลให้อยู่ในรูปแบบการเขียน ใช้กฎได้ง่ายขึ้น รูปแบบหนึ่งที่ใช้กันทั่วไปในการจัดเก็บข้อมูลผู้ใช้ใน Realtime Database คือ เพื่อจัดเก็บผู้ใช้ทั้งหมดไว้ในโหนด users เดียวที่มีระดับผู้เผยแพร่โฆษณาย่อย ค่า uid สำหรับผู้ใช้ทุกคน ถ้าต้องการจำกัดการเข้าถึง ข้อมูลนี้ซึ่งมีเพียงผู้ใช้ที่เข้าสู่ระบบเท่านั้นที่จะเห็นข้อมูลของตนเอง กฎของคุณ จะมีหน้าตาแบบนี้

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth !== null && auth.uid === $uid"
      }
    }
  }
}

การใช้การอ้างสิทธิ์ที่กำหนดเองของการตรวจสอบสิทธิ์

การตรวจสอบสิทธิ์ Firebase สำหรับแอปที่ต้องมีการควบคุมการเข้าถึงที่กำหนดเองสำหรับผู้ใช้แต่ละคน อนุญาตให้นักพัฒนาซอฟต์แวร์ตั้งค่าการอ้างสิทธิ์กับผู้ใช้ Firebase ได้ คุณจะเข้าถึงการอ้างสิทธิ์เหล่านี้ได้ในตัวแปร auth.token ในกฎ ต่อไปนี้คือตัวอย่างของกฎที่ใช้ hasEmergencyTowel การอ้างสิทธิ์ที่กำหนดเอง:

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

นักพัฒนาซอฟต์แวร์ที่สร้าง ของตนเอง โทเค็นการตรวจสอบสิทธิ์ที่กำหนดเองสามารถเพิ่มการอ้างสิทธิ์ลงในโทเค็นเหล่านี้ได้ เหล่านี้ ใช้ได้กับตัวแปร auth.token ในกฎ

ข้อมูลที่มีอยู่กับข้อมูลใหม่

ตัวแปร data ที่กำหนดไว้ล่วงหน้าใช้เพื่ออ้างอิงข้อมูลก่อน จะมีการดำเนินการเขียน ในทางกลับกัน ตัวแปร newData มีข้อมูลใหม่ที่จะปรากฏหากการเขียนสำเร็จ newData แสดงผลลัพธ์ที่ผสานรวมข้อมูลใหม่ที่เขียน และข้อมูลที่มีอยู่

เพื่อให้เห็นภาพ กฎนี้ทำให้เราสามารถสร้างระเบียนใหม่ หรือลบที่มีอยู่ แต่ไม่ทำการเปลี่ยนแปลงกับข้อมูลที่มีอยู่

// we can write as long as old data or new data does not exist
// in other words, if this is a delete or a create, but not an update
".write": "!data.exists() || !newData.exists()"

การอ้างอิงข้อมูลในเส้นทางอื่นๆ

สามารถใช้ข้อมูลใดๆ เป็นเกณฑ์สำหรับกฎได้ การใช้ฟิลด์ที่กำหนดไว้ล่วงหน้า ตัวแปร root, data และ newData เรา จะเข้าถึงเส้นทางใดก็ได้ตามที่มีอยู่ก่อนหรือหลังเหตุการณ์การเขียน

ลองพิจารณาตัวอย่างนี้ ซึ่งอนุญาตให้เขียนตราบเท่าที่ค่าของแอตทริบิวต์ /allow_writes/ โหนดคือ true แต่โหนดหลักไม่มี ตั้งค่าแฟล็ก readOnly แล้ว และมีเด็กที่ชื่อ foo ใน ข้อมูลที่เขียนใหม่:

".write": "root.child('allow_writes').val() === true &&
          !data.parent().child('readOnly').exists() &&
          newData.child('foo').exists()"

กำลังตรวจสอบข้อมูล

การบังคับใช้โครงสร้างข้อมูลและตรวจสอบรูปแบบและเนื้อหาข้อมูลควร ทำได้โดยใช้กฎ .validate ซึ่งจะทำงานหลังจาก กฎ .write ข้อให้สิทธิ์ในการเข้าถึงสำเร็จแล้ว ด้านล่างนี้เป็นตัวอย่าง คำจำกัดความของกฎ .validate รายการซึ่งอนุญาตเฉพาะวันที่ในรูปแบบ YYYY-MM-DD ระหว่างปี 1900-2099 ซึ่งตรวจสอบโดยใช้นิพจน์ทั่วไป

".validate": "newData.isString() &&
              newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"

กฎ .validate เป็นกฎความปลอดภัยประเภทเดียวที่ไม่ต่อเนื่อง หากมี กฎการตรวจสอบความถูกต้องล้มเหลวในระเบียนย่อย การดำเนินการเขียนทั้งหมดจะถูกปฏิเสธ นอกจากนี้ คำจำกัดความของการตรวจสอบความถูกต้องจะถูกละเว้นเมื่อมีการลบข้อมูล (กล่าวคือ เมื่อค่าใหม่) ที่กำลังเขียนอยู่คือ null)

เรื่องเหล่านี้อาจดูเป็นเพียงประเด็นเล็กๆ แต่อันที่จริงแล้วเป็นฟีเจอร์สำคัญสำหรับการเขียน กฎการรักษาความปลอดภัยฐานข้อมูลเรียลไทม์ของ Firebase ที่มีประสิทธิภาพ โดยพิจารณากฎต่อไปนี้

{
  "rules": {
    // write is allowed for all paths
    ".write": true,
    "widget": {
      // a valid widget must have attributes "color" and "size"
      // allows deleting widgets (since .validate is not applied to delete rules)
      ".validate": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99
        ".validate": "newData.isNumber() &&
                      newData.val() >= 0 &&
                      newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical
        // /valid_colors/ index
        ".validate": "root.child('valid_colors/' + newData.val()).exists()"
      }
    }
  }
}

พิจารณาผลลัพธ์ของการดำเนินการเขียนต่อไปนี้โดยคำนึงถึงตัวแปรนี้

JavaScript
var ref = db.ref("/widget");

// PERMISSION_DENIED: does not have children color and size
ref.set('foo');

// PERMISSION DENIED: does not have child color
ref.set({size: 22});

// PERMISSION_DENIED: size is not a number
ref.set({ size: 'foo', color: 'red' });

// SUCCESS (assuming 'blue' appears in our colors list)
ref.set({ size: 21, color: 'blue'});

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child('size').set(99);
Objective-C
หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"];

// PERMISSION_DENIED: does not have children color and size
[ref setValue: @"foo"];

// PERMISSION DENIED: does not have child color
[ref setValue: @{ @"size": @"foo" }];

// PERMISSION_DENIED: size is not a number
[ref setValue: @{ @"size": @"foo", @"color": @"red" }];

// SUCCESS (assuming 'blue' appears in our colors list)
[ref setValue: @{ @"size": @21, @"color": @"blue" }];

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
[[ref child:@"size"] setValue: @99];
Swift
หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
var ref = FIRDatabase.database().reference().child("widget")

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo")

// PERMISSION DENIED: does not have child color
ref.setValue(["size": "foo"])

// PERMISSION_DENIED: size is not a number
ref.setValue(["size": "foo", "color": "red"])

// SUCCESS (assuming 'blue' appears in our colors list)
ref.setValue(["size": 21, "color": "blue"])

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("widget");

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo");

// PERMISSION DENIED: does not have child color
ref.child("size").setValue(22);

// PERMISSION_DENIED: size is not a number
Map<String,Object> map = new HashMap<String, Object>();
map.put("size","foo");
map.put("color","red");
ref.setValue(map);

// SUCCESS (assuming 'blue' appears in our colors list)
map = new HashMap<String, Object>();
map.put("size", 21);
map.put("color","blue");
ref.setValue(map);

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
REST
# PERMISSION_DENIED: does not have children color and size
curl -X PUT -d 'foo' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION DENIED: does not have child color
curl -X PUT -d '{"size": 22}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION_DENIED: size is not a number
curl -X PUT -d '{"size": "foo", "color": "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# SUCCESS (assuming 'blue' appears in our colors list)
curl -X PUT -d '{"size": 21, "color": "blue"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# If the record already exists and has a color, this will
# succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
# will fail to validate
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

ตอนนี้เรามาดูโครงสร้างเดียวกัน แต่ใช้กฎ .write แทน .validate

{
  "rules": {
    // this variant will NOT allow deleting records (since .write would be disallowed)
    "widget": {
      // a widget must have 'color' and 'size' in order to be written to this path
      ".write": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE
        ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical valid_colors/ index
        // BUT ONLY IF WE WRITE DIRECTLY TO COLOR
        ".write": "root.child('valid_colors/'+newData.val()).exists()"
      }
    }
  }
}

ในตัวแปรนี้ การดำเนินการต่อไปนี้จะสำเร็จ

JavaScript
var ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.set({size: 99999, color: 'red'});

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child('size').set(99);
Objective-C
หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
Firebase *ref = [[Firebase alloc] initWithUrl:URL];

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
[ref setValue: @{ @"size": @9999, @"color": @"red" }];

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
[[ref childByAppendingPath:@"size"] setValue: @99];
Swift
หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
var ref = Firebase(url:URL)

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.setValue(["size": 9999, "color": "red"])

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.childByAppendingPath("size").setValue(99)
Java
Firebase ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
Map<String,Object> map = new HashMap<String, Object>();
map.put("size", 99999);
map.put("color", "red");
ref.setValue(map);

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child("size").setValue(99);
REST
# ALLOWED? Even though size is invalid, widget has children color and size,
# so write is allowed and the .write rule under color is ignored
curl -X PUT -d '{size: 99999, color: "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# ALLOWED? Works even if widget does not exist, allowing us to create a widget
# which is invalid and does not have a valid color.
# (allowed by the write rule under "color")
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

ตัวอย่างนี้แสดงความแตกต่างระหว่างกฎ .write กับกฎ .validate ดังที่แสดงไว้ กฎทั้งหมดเหล่านี้ควรเขียนโดยใช้ .validate พร้อมด้วย ข้อยกเว้นที่เป็นไปได้ของกฎ newData.hasChildren() ซึ่งขึ้นอยู่กับว่า ควรอนุญาตให้มีการลบ

กฎที่อิงตามคำค้นหา

แม้ว่าคุณจะใช้กฎเป็นตัวกรองไม่ได้ แต่คุณ จำกัดการเข้าถึงข้อมูลชุดย่อยได้โดยใช้พารามิเตอร์การค้นหาในกฎ ใช้นิพจน์ query. ในกฎเพื่อให้สิทธิ์อ่านหรือเขียนตาม พารามิเตอร์การค้นหา

เช่น กฎที่อิงตามคำค้นหาต่อไปนี้ใช้กฎความปลอดภัยที่อิงตามผู้ใช้ และกฎที่อิงตามคำค้นหาเพื่อจำกัดการเข้าถึงข้อมูลในคอลเล็กชัน baskets เฉพาะตะกร้าช็อปปิ้งที่ผู้ใช้ที่ใช้งานอยู่เป็นเจ้าของเท่านั้น

"baskets": {
  ".read": "auth.uid !== null &&
            query.orderByChild === 'owner' &&
            query.equalTo === auth.uid" // restrict basket access to owner of basket
}

ข้อความค้นหาต่อไปนี้ ซึ่งมีพารามิเตอร์การค้นหาในกฎจะ สำเร็จ:

db.ref("baskets").orderByChild("owner")
                 .equalTo(auth.currentUser.uid)
                 .on("value", cb)                 // Would succeed

แต่คำค้นหาที่ไม่มีพารามิเตอร์ในกฎจะดำเนินการไม่สำเร็จ ข้อผิดพลาด PermissionDenied:

db.ref("baskets").on("value", cb)                 // Would fail with PermissionDenied

คุณยังใช้กฎที่อิงตามคำค้นหาเพื่อจำกัดปริมาณข้อมูลที่ไคลเอ็นต์ดาวน์โหลดได้ด้วย ผ่านการดำเนินการอ่าน

ตัวอย่างเช่น กฎต่อไปนี้จำกัดสิทธิ์การเข้าถึงระดับอ่านให้เฉพาะ 1, 000 รายการแรก ของข้อความค้นหาตามลำดับความสำคัญ:

messages: {
  ".read": "query.orderByKey &&
            query.limitToFirst <= 1000"
}

// Example queries:

db.ref("messages").on("value", cb)                // Would fail with PermissionDenied

db.ref("messages").limitToFirst(1000)
                  .on("value", cb)                // Would succeed (default order by key)

นิพจน์ query. ต่อไปนี้มีอยู่ในกฎความปลอดภัยของฐานข้อมูลแบบเรียลไทม์

นิพจน์กฎที่อิงตามข้อความค้นหา
นิพจน์ ประเภท คำอธิบาย
query.orderByKey
query.orderByPriority
query.orderByValue
boolean เป็นจริงสำหรับคำค้นหาที่เรียงตามคีย์ ลำดับความสำคัญ หรือค่า เป็นเท็จ
query.orderByChild สตริง
ค่าว่าง
ใช้สตริงเพื่อแสดงเส้นทางที่เกี่ยวข้องไปยังโหนดย่อย ตัวอย่างเช่น query.orderByChild === "address/zip". หากคำค้นหาไม่ใช่ เรียงลำดับโดยโหนดย่อยค่านี้เป็นค่าว่าง
query.startAt
query.endAt
query.equalTo
สตริง
หมายเลข
บูลีน
ค่าว่าง
เรียกข้อมูลขอบเขตของคำค้นหาที่เรียกใช้ หรือแสดงผลเป็น Null หากมี ไม่ได้กำหนดขอบเขต
query.limitToFirst
query.limitToLast
หมายเลข
ค่าว่าง
เรียกคืนขีดจำกัดของการค้นหาที่ดำเนินการอยู่ หรือแสดงผลค่า Null หากมี ไม่จำกัด

ขั้นตอนถัดไป

หลังจากปรึกษาหารือเกี่ยวกับเงื่อนไขกันแล้ว คุณมีทางเลือกมากขึ้น เข้าใจกฎต่าง ๆ และพร้อมที่จะ:

เรียนรู้วิธีจัดการกรณีการใช้งานหลักและเรียนรู้เวิร์กโฟลว์สำหรับการพัฒนา การทดสอบและการใช้งานกฎ:

ดูข้อมูลเกี่ยวกับฟีเจอร์กฎที่มีกับ Realtime Database โดยเฉพาะ