Realtime Database là gì

Firebase, Bài 2.1: Giới thiệu về Realtime Database.

Bảo Huy
5 years ago

Nhắc tới Firebase thì không thể không nhắc tới Realtime Database tính năng khởi thủy của Ngọn lửa. Đây cũng là dịch vụ trung tâm trong hệ thống các dịch vụ khác của Firebase. Với ưu điểm cực kì lớn là tính realtime được cấu hình và thực hiện sẵn trong hậu cảnh [background], các lập trình viên chỉ còn mỗi công việc vận dụng các hàm có sẵn để dựng ứng dụng mà thôi. Cũng như các bài viết trước, chúng tôi sẽ giới thiệu sơ lược về Firebase Realtime Database trên Android, iOS và Web. Đối với các framework khác như React Native, chúng tôi sẽ trình bày sau nếu có dịp.

1. Firebase Realtime Database là gì?

Store and sync data with our NoSQL cloud database. Data is synced across all clients in realtime, and remains available when your app goes offline.

The Firebase Realtime Database is a cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client. When you build cross-platform apps with our iOS, Android, and JavaScript SDKs, all of your clients share one Realtime Database instance and automatically receive updates with the newest data.

Có lẽ các bạn đã hiểu rất rõ nghĩa của từ database, tức là cơ sở dữ liệu. Tuy nhiên, realtime là gì? Bạn hẵn đã từng nghe qua realtime rendering, realtime notification, realtime checking. Vui lòng không nên dịch một cách từng chữ từ realtime ra thời gian thực vì đó là một từ khá vô nghĩa.

Hãy hình dung bạn đang trò chuyện với một hoặc nhiều người đối diện. Khi bạn nói, họ ngay lập tức tiếp nhận lời của bạn, và sau đó họ phản hồi lại, và bạn cũng lập tức tiếp nhận các thông tin được truyền tải trong lời của họ. Hai người luân phiên hỏi-đáp và tiếp nhận thông tin một cách tức thời. Ta nói quá trình đó được diễn ra một cách realtime. Như vậy, ta có thể hiểu nghĩa cúa realtime là liên tục và ngay lập tức.

Kết hợp realtime và database, ta biết đó là một cơ sở dữ liệu được cập nhật liên tục và không gián đoạn dựa trên các tương tác của thiết bị khách [client] và máy chủ [server, ở đây là Firebase server]. Khi một thiết bị gửi các thông tin về sự thay đổi, bao gồm vị trí [position, tức là key] và nội dung [content, tức là value], Firebase Realtime Database ngay lập tức phân tích và áp dụng các thay đổi đó, và đồng thời gửi ngay các thay đổi đó xuống các thiết bị khác đang cùng lắng nghe [listen] cùng một cơ sở dữ liệu đó để chúng cập nhật dữ liệu ngay.

Ứng dụng cơ bản nhất, rõ ràng nhất của Firebase Realtime Database [FRD] là làm ứng dụng chat. Và hầu như, bạn nào khi tìm hiểu về FRD đều làm một ứng dụng chat để cho biết. Tuy nhiên, vẫn có đó những tiềm năng, những ứng dụng khác của FRD mà bạn có thể sáng tạo vận dụng.

2. Mô hình của Firebase Realtime Database.

Có lẽ các bạn đã ít nhiều quen thuộc với SQL Database. SQL Database được tổ chức dưới dạng các bảng, mỗi bảng bao gồm các hàng và các cột. Các cột đại diện cho các thuộc tính của các đối tượng, trong khi các hàng đại diện cho các đối tượng. Ví dụ, ta có bảng quản lí sinh viên, trong đó các cột là STT, Họ, Tên, Giới tính, Ngày sinh, Địa chỉ liên hệ, còn mỗi hàng là một sinh viên cụ thể. Có bao nhiêu sinh viên thì sẽ có bấy nhiêu hàng tương ứng.

Firebase Realtime Database không được tổ chức như vậy. Nó được tổ chức theo dạng cây [trees], giống như dạng cây thư mục [folder tree] mà các bạn đã quá quen thuộc trong Windows Explorer. Tuy nhiên, một nhánh [branch] không được chứa đồng thời nhiều dữ liệu khác nhau. Trong Windows Explorer 1 thư mục mẹ có thể chứa nhiều thư mục con và các tập tin nằm ngang hàng với các thư mục con kia, trong thư mục con lại có các thư mục cháu và các tập tin cùng hàng với thư mục cháu. Trong Firebase Realtime Database, mỗi nhánh giống như một container, chỉ chứa hoặc là dữ liệu ứng với nhánh đó [tức là value tương ứng với key], hoặc một tập hợp các nhánh con cũng được tổ chức theo một cách tương tự.

Mỗi branch được đại diện bởi một key và value tương ứng. Luôn luôn, các keys luôn có dạng là [NS]String. Còn các values thì, như tôi đã trình bày phía trên, có thể trực tiếp là một đối tượng String, một đối tượng Int, hay là một Object chứa nhiều thông tin bên trong, được thể hiện dưới dạng một nhánh con. Và nhánh con đó được coi như là một value ứng với key đó. Do được tổ chức theo hình thức này, mà dữ liệu được gửi về các thiết bị khách sẽ đương nhiên có dạng là JSON.

Các key cùng tầng phải là duy nhất. Các key kháctầng có thể được phép giống nhau. Để đảm bảo các key cùng tầng là duy nhất, thì Ngọn lửa cung cấp cho chúng ta các hàm để yêu cầu Firebase server tạo ngẫu nhiên các giá trị key duy nhất, chẳng hạn như các KI2 keys trong hình minh họa bên trên. Đó là push[] trong Android và Web, hay childByAutoId[] trên iOS.

3. Nguyên tắc cập nhật các values.

Khi bạn thêm một bộ dữ liệu mới, tức bao gồm 1 bộ key value mới, thì sẽ xảy ra hai trường hợp sau.

  • Key chưa tồn tại: Firebase Database sẽ tạo một Object mới tại vị trí mà bạn muốn lưu, với K-V là các giá trị bạn đã định nghĩa. Event này được gọi là add, tức là thêm.
  • Key đã tồn tại: Firebase Database sẽ sửa value tại vị trí của key đó theo value mới. Event này được gọi là change.

Ví dụ, đối với cơ sở dữ liệu trong hình trên,bạn sẽ thấy là các keys KI2 trong users/users/ có giá trị hoàn toàn là duy nhất. Nếu tôi add 1 Object có key là KI2H0sKk5A6Ny1plBly thì nguyên cả bộ fieldName: messageField và text:this is my third message sẽ bị thay đổi thành giá trị mới. Tóm lại, khi bạn đưa 1 object lên FRD, nếu chưa có key đó thì FRD sẽ tạo một Objectcó key mà bạn muốn và add value. Còn nếu đã tồn tại key rồi thì nó sẽ sửa đổi value tại vị trí key đó. Trường hợp bạn dùng hàm push[] hoặc childByAutoId[] thì CSDL của Ngọn lửa sẽ gián tiếp tạo một Object có key là một [NS]String ngẫu nhiên [và dài dòng] và value là cái Object của bạn.

4. Cái hayvà hạn chế của Firebase Realtime Database:

Như bạn đã thấy, FRD được tổ chức theo dạng cây nhánh chứ không phải dạng hàng cột và đây có thể là một vấn đề hết sức nan giải cho những lập trình viên đã và đang thiết kế cơ sở dữ liệu dạng bảng. Thực ra không hẳn là một hạn chế, vì FRD được tạo ra dựa trên ý tưởng này ngay từ đầu. Và họ cũng không có ý định cho ra mắt thêm một dạng cơ sở dữ liệu dạng bảng, ít nhất là cho tới thời điểm hiện tại.

Rõ ràng, cơ cấu tổ chức của FRD tỏ ra có nhiều lợi thế rõ rệt hơn so với dạng bảng của SQLDatabase [SQLD]. Chẳng hạn, FRD thích hợp hơn hẳn để làm ứng dụng chat. Hơn thế nữa, FRD tỏ ralinh hoạt [flexible] hơn so với SQLD, chẳng hạn như việc bạn thêm một cột mới vào CSDL SQLD đã có là cả một vấn đề, đặc biệt nhất là vấn đề về sự tương thích giữa nhiều phiên bản ứng dụng chạy trên thiết bị khách [client], chưa kể đến việc các câu Schema phải chuẩn nữa. Trong khi đó, đối với FRD, bạn chỉ cần thêm một trường mới vào Object của mình. Đối với các Object cũ, các trường không có dữ liệu sẽ đơn giản là được trả về kết quả null [Android, web] hoặc nil [iOS, macOS].

Ví dụ, bạn đang có một class Student[String name, String birthday]. Đối với SQLD, bạn tạo một CSDL có hai cột là NAME và BIRTHDAY. Đối với FRD, bạn chỉ cần 2 nhánh name và birthday trong một Object làm value là được. Sau khi đã sử dụng CSDL đó để nhập liệu được gần 2 năm thì bấy giờ, bạn được yêu cầu bổ sung thêm một trường nữa là lớp học và cột này phải là NON NULL. Vậy, bạn làm cách nào? Đối với SQLD, bạn sẽ ALTER TABLE để thêm một cột. Ố kề, xong câu Schema rồi và đã có cột mới. Ơ, nhưng có cái gì đó sai sai, chẳng hạn như những học sinh đầu tiên đã có thông tin về lớp học đâu? Khi fetch thông tin về thì chắc chắn sẽ xảy ra mâu thuẫn, và đối với một lập trình viên thuộc dạng hay lấy cụm từ em mới học ra để bào biện thì nước biển sẽ mặn lắm.

Còn đối với FRD thì bạn chỉ đơn giản là bổ sung thêm một trường mới vào Student object của bạn, tức là Student[String name, String birthday, String class]. Đối với các học sinh không có thông tin về class, thì khi bạn fetch các thông tin về, String class chỉ đơn giản là bị null/nil và bạn chỉ cần xét if [class != null] là được.

Tuy nhiên, nếu bạn cần lắm sự kết nối giữa các hàng trong một bảng, chẳng hạn như việc quản lí tiền lương thì SQLD cũ kĩ vẫn sẽ tỏ ra vượt trội hơn, mặc dù bạn vẫn có khả năng xoay sở đối với FRD. Còn nếu bạn phải quản lí các dữ liệu theo kiểu liên bảng, tức là bạn phải thực hiện thao tác JOIN các dữ liệu trong SQLD, có thểviệc thực hiện các thao tác thay thế trên FRD trên thực tế sẽ khó khăn hơn là bạn nghĩ.

Một vấn đề quan trọng hơn nữa, là PRIMARY KEY. Các bạn ít nhiều đã biết rồi, PRIMARY KEY là một trong những điều cần thiết để quản lí hiệu quả CSDL SQLD. Tuy nhiên trong FRD thì hoàn toàn không tồn tại khái niệm PRIMARY KEY, do đó bạn sẽ cần cẩn thận trong việc quản lí các objects để tránh bị cập nhật sai các dữ liệu đã có sẵn.

Tóm lại, Firebase Realtime Database ngoài ưu điểm mang tính realtime thì còn có tính linh hoạt rất cao, dễ dàng trong việc bổ sung hoặc cắt bớt các trường trong một Object. Tuy nhiên, sự liên hệ tương ứng giữa các đối tượng trong FRD sẽ là một bài toán không hề dễ và cách xử lí phụ thuộc rất nhiều vào trình độ và kinh nghiệm của lập trình viên, cũng như bản chất và mục đích của bản thân CSDL được tạo.

5. Tích hợp Firebase Realtime Database vào App project và sơ lược về các Value Events.

Như thường lệ, chúng tôi sẽ hướng dẫn các bạn tích hợp Firebase Realtime Database vào các dự án ứng dụng cho Android, iOS [Swift] và Web. Nhưng trước hết, hãy đảm bảo là bạn đã tích hợp Firebase vào project của mình. Chúng tôi cũng khuyến nghị bạn nên sử dụng Authenticator để bảo vệ hệ thống dữ liệu. Xin vui lòng xem các bài viết trước để có thêm thông tin. Nếu bạn muốn tạm thời bỏ qua Authentication, hãy cập nhật Database Rules thông qua Firebase Console như sau:

{ "rules": { ".read": true, ".write": true } }

5.1. Đối với Android:

Để thông báo cho Gradle compile thêm bộ thư viện Realtime Database, bạn chỉ việc thêm dòng sau vào tập tin build.gradle:app như các lần trước:

compile 'com.google.firebase:firebase-database:+'

Ta tiếp tục khai báo các instance như bên dưới. Các event sẽ được handle bởi DatabaseReference myRef.

String path = "..." // your path // FirebaseDatabase database = FirebaseDatabase.getInstance[]; DatabaseReference myRef = database.getReference[path];

Trong đó, path là đường dẫn tới tới khu vực mà bạn muốn thao tác trong FRD. Mặc định là FRD sẽ thả bạn ở vị trí gốc, tức là brilliant-fire-3159 như trong hình minh họa.

Nếu tôi muốn truy cập vào users ngay dưới brilliant-fire-3159 thì tôi chỉ việc gán cho path là users mà thôi, nghĩa là:

String path = "users"; FirebaseDatabase database = FirebaseDatabase.getInstance[]; DatabaseReference myRef = database.getReference[path];

Còn nếu tôi muốn truy cập trực tiếp vào b797bdd4-a193, tức là tôi chỉ thao tác từ đó trở vào bên trong của nó thì thôi sẽ gán giá trị cho path như sau:

String path = "users/users/b797bdd4-a193..."; FirebaseDatabase database = FirebaseDatabase.getInstance[]; DatabaseReference myRef = database.getReference[path];

Nhưng tốt hơn hết là bạn nên dùng hàm child[String child], và hàm này thì tôi sẽ trình bày trong các bài viết tới. Dưới đây là ví dụ:

FirebaseDatabase database = FirebaseDatabase.getInstance[]; DatabaseReference myRef = database.getReference[].child["users"].child["users"].child["b797bdd4-a193..."];

Sau khi đã trỏ tới địa chỉ mong muốn thì bạn có thể setValue tương ứng với key đó, chẳng hạn như tôi sẽ setValue là một String This is my value thì tôi làm như sau.

myRef.setValue["This is my value"];

Còn nếu tôi muốn tạo một Object có key ngẫu nhiên để bọc lại Object[fieldName, text] của tôi với hàm push[] thì tôi làm như sau:

String fieldName, text; Data data = new Data[fieldName, text]; myRef.push[].setValue[data]; // Trong đó, Data là một class đối tượng chứa các thuộc tính như trong hình minh họa class Data { String fieldName, text; // Constructor rỗng, buộc phải có để có thể cast kiểu dữ liệu được. Data[] { } // Constructor đầy đủ tham số Data[String fieldName, String text] { this.fieldName = fieldName; this.text = text; } static fromMap[Map map] { String fieldName = map.getString["fieldName"]; String text = map.getString["text"]; return new Data[fieldName, text]; } }

Đối với các ValueEventListener thì bạn cũng gán listener vào myRef, cụ thể:

myRef.addValueEventListener[new ValueEventListener[] { @Override public void onDataChange[DataSnapshot snapshot] { // Khi data thay đổi Data data = Data.fromMap[snapshot.getValue[Map.class]]; String fieldName = data.fieldName; String text = data.text; Log.d[TAG, "Value is: " + value]; } @Override public void onCancelled[DatabaseError error] { // Khi gặp lỗi. Log.w[TAG, "Failed to read value.", error.toException[]]; } }];

5.2. Đối với iOS:

Bạn đã quá quen thuộc với việc sử dụng CocoaPods để thêm các thư viện Firebase vào Xcode project của mình rồi, và lần này cũng không là ngoại lệ:

pod 'Firebase/Database'

Tiếp tục, ta tạo các instances như sau:

var ref: FIRDatabaseReference! ref = FIRDatabase.database[].reference[]

Mặc định là FRD sẽ thả bạn ở vị trí gốc, tức là brilliant-fire-3159 như trong hình minh họa dưới đây. Nếu bạn muốn truy cập vào bên trong thì dùng hàm child[]. Tôi sẽ trình bày về child[] trong các bài viết sau.

Nếu tôi muốn truy cập vào users ngay dưới brilliant-fire-3159 thì tôi chỉ việcgán ref như bên dưới:

ref = FIRDatabase.database[].reference[].child["users"] // Users

Còn nếu tôi muốn truy cập trực tiếp vào b797bdd4-a193, tức là tôi chỉ thao tác từ đó trở vào bên trong, thì ngoài hàm child[], tôi có thể dùng referenceWithPath[path] cho refnhư sau:

ref = FIRDatabase.database[].referenceWithPath["users/users/b797bdd4-a193..."]

Sau khi đã trỏ tới địa chỉ mong muốn thì bạn có thể setValue tương ứng với key đó, chẳng hạn như tôi sẽ setValue là một String This is my value thì tôi làm như sau.

ref.setValue["This is my value"]

Còn nếu tôi muốn tạo một Object có key ngẫu nhiên để bọc lại Object[fieldName, text] của tôi với hàm childByAutoId[] thì tôi làm như sau:

var fieldName: String var text: String var data = Data[fieldName, text] ref.childByAutoId[].setValue[data] // Trong đó, Data là một class đối tượng chứa các thuộc tính như trong hình minh họa class Data { var fieldName: String var text: String // Init rỗng, buộc phải có để có thể cast kiểu dữ liệu được. init[] { } // Init đầy đủ tham số init[fieldName: String, text: String] { self.fieldName = fieldName self.text = text } init[dictionary: [String:AnyObject]] { self.fieldName = dictionary["fieldName"] as! String self.text = dictionary["text"] as! String } }

Để quan sát sự thay đổi của value, ta cũng sẽ thao tác với biến ref. Cụ thể là như bên dưới:

ref.observe[FIRDataEventType.value, with: { [snapshot] in let data = Data[snapshot.value as? [String:AnyObject]] let fieldName = data.fieldName let text = data.text }]

5.3. Đối với Web:

Việc thêm Firebase Realtime Database SDK hoàn toàn tương tự và đơn giản như các sản phẩm khác. Trong JS, các bạn chuẩn bị các instances như sau, lưu ý là bạn phải điều chỉnh các giá trị cho đúng với Firebase project của bạn:

var config = { apiKey: "apiKey", authDomain: "projectId.firebaseapp.com", databaseURL: "//databaseName.firebaseio.com", storageBucket: "bucket.appspot.com" }; firebase.initializeApp[config]; var database = firebase.database[];

Mặc định là FRD sẽ thả bạn ở vị trí gốc, tức là brilliant-fire-3159 như trong hình minh họa dưới đây. Nếu bạn muốn truy cập vào bên trong thìbạn cho ghép String hoặc dùng hàm child[String child].

Nếu tôi muốn truy cập vào users ngay dưới brilliant-fire-3159 thì tôi chỉ việc gán cho path là users mà thôi, nghĩa là:

var path = "users"; var ref = database.ref[path];

Còn nếu tôi muốn truy cập trực tiếp vào b797bdd4-a193, tức là tôi chỉ thao tác từ đó trở vào bên trong của nó thì thôi sẽ gán giá trị cho path như sau:

var path = "users/users/b797bdd4-a193..."; var ref = database.ref[path];

Hoặc nếu dùng hàm child[String child] thì tôi có hàm bên dưới:

var ref = database.ref[].child["users"].child["users"].child["b797bdd4-a193..."];

Sau khi đã trỏ tới địa chỉ mong muốn thì bạn có thể setValue tương ứng với key đó, chẳng hạn như tôi sẽ setValue là một String This is my value thì tôi làm như sau.

ref.set["This is my value"];

Còn nếu tôi muốn tạo một Object có key ngẫu nhiên để bọc lại Object[fieldName, text] của tôi với hàm push[] thì tôi làm như sau:

ref.push[].set[{ fieldName: "...", text: "..." }];

Để lắng nghe sự thay đổi của value, ta cũng sẽ thao tác với biến ref. Cụ thể là như sau:

ref.on['value', function[snapshot] { // Code xử lí ở đây. let data = snapshot.val[]; let fieldName = data.fieldName; let text = data.text; }];

Vậy là bạn đã xong các bước cơ bản để thao tác với Firebase Realtime Database rồi đó. Hẹn gặp lại trong các bài viết sắp tới.

Categories: Android, Swift, Web
Tags: android, firebase, ios, web
Leave a Comment

Video liên quan

Chủ Đề