Trời tính không bằng máy tính

blog, tutorial

#20: Một vài “Mẹo” hay với Firebase

Firebase là dạng 1 NoSQL database mới nổi từ 3-4 nãm trở lại đây. Mình đã từng dùng firebase từ khi nó còn là bản Beta, khi đó vốn hiểu biết về code của mình còn khá hạn hẹp, và firebase đã làm đơn giản hóa về phần code đi rất nhiều.

Ảnh: 1 trong những project dùng firebase khá lớn mà mình đã từng tham gia

Firebase có khá nhiều ưu điểm và cũng khá nhiều hạn chế so với các loại database mà bạn có thể tự cài trên localhost. 2 ưu điểm lớn nhất của firebase đó là sử dụng đơn giản, và tốc độ cao (do dùng trên server google hết mà). Nhưng đổi lại, bạn sẽ gặp nhiều hạn chế như scale càng cao, chi phí càng đắt (đòi hỏi bạn phải chăm tối ưu hóa), hay thậm chí không có các lệnh tương đương JOIN, GROUP BY, LIKE, OR…

Trong bài này, mình tổng hợp lại 1 vài “mẹo” mà các bạn sẽ khó kiếm đc trên mạng để giải quyết phần nào các hạn chế này

1. Full-text search (tìm kiểu danh bạ)

Firebase không hỗ trợ việc query string “gần giống” (như lệnh LIKE trong SQL). Điều này khá tệ vì những việc đơn giản như tìm theo tên, theo SĐT thôi cũng trở nên phức tạp.

Tuy chính trên documentation họ cũng đã có giải pháp là dùng algolia, nhưng về chi phí khá đắt và algolia cũng chỉ cho free trial 14 ngày mà thôi.

Ở đây mình sử dụng Firestore. Cách mình làm đó là mình tự tạo các trường hợp con cho 1 string, rồi lưu chung vào 1 array và dùng điều kiện "array-contains", ví dụ như sau:

Với tên "Nguyen Xuan Son", mình tạo ra 1 array dạng ["ngu", "nguy", "nguye", "nguyen", "xua", "xuan", "son"]. Bạn có thể tham khảo code tại đây: https://gist.github.com/ngxson/bab6a0f61d9161c7dd13995895eb3938#file-generatesearchindex-js

Sau đó nếu người dùng cần tìm tên Sơn, mình sẽ tạo lệnh truy vấn dạng ref.where("search", "array-contains", "son")

Cách này tuy ko thể truy vấn đề trong văn bản, string dài, nhưng vẫn có thể giúp xây dựng phần tìm theo tên hay SĐT

2. Chỉ lấy về tên key (mà không lấy data trong key đó)

Giờ hãy ví dụ bạn lưu 1 danh sách dài (khoảng vài nghìn users), đây chắc hẳn là cấu trúc bạn nghĩ ngay tới:

Tuy nhiên, chuyện gì xảy ra nếu bạn muốn chỉ lấy danh sách ID (chính là key trong object users). Chắc chắn bạn sẽ phải tải toàn bộ data về chỉ để lấy key, và chắc chắn hiệu suất của cách làm này không cao.

Thực tế, ở REST API có cung cấp 1 field là “shallow”, cho phép chỉ nhận về các keys trong 1 path cụ thể trong database. Tuy vậy, để dùng REST API, bạn sẽ cần tạo 1 access_token. Hơi lằng nhằng nhưng bạn có thể dùng code do mình viết sẵn: https://gist.github.com/ngxson/bab6a0f61d9161c7dd13995895eb3938#file-fetchkeyonly-js

Code khá phức tạp nhưng dữ liệu trả về sẽ theo dạng như trên ảnh, giờ bạn có thể dùng key này để làm “trò” thứ 3 rồi 😉

3. Chỉ trả về 1 trường nhất định

Trong tất cả các ngôn ngữ truy vấn khác, bạn đều có thể chỉ ra chính xác các trường muốn trả về.

Ví dụ như với ảnh chụp database ở mục 2, nhỡ đâu mình muốn lấy danh sách ID – giới tính của user thôi thì sao?

Cách làm của mình đó là đầu tiên mình sẽ lấy danh sách toàn bộ key (như mục 2), sau đó tạo 1 loạt query dạng /user/{user-id}/gender, rồi dùng Promise.all để xử lý sau khi mọi truy vấn đã thực hiện xong:

Code tham khảo: https://gist.github.com/ngxson/bab6a0f61d9161c7dd13995895eb3938#file-getonlygender-js

Cách này nghe có vẻ là nặng nề và kém hiệu quá, nhưng thực tế nó khá nhanh do:

  • Server của google vốn nhanh sẵn, dăm ba nghìn cái request 1 lúc ko sao
  • Mọi request đều đi qua 1 connection (websocket) duy nhất

4. Mẹo dùng rules (realtime database)

Mình mất 1 thời gian khá lâu để hiểu rules của firebase, vì tài liệu cho nó khá hiếm và cũng khá khó hiểu. Sau đây mình sẽ tổng hợp 1 vài rules mình hay dùng:

Không cho phép xóa dữ liệu:
".write": "newData.exists()"

Không cho phép ghi đè dữ liệu:
".write": "!data.exists()"

Cho phép user lưu data mới, và data mới bắt buộc có trường uid là user_id của họ:
".validate": "newData.child('uid').val() === auth.uid"

Phân quyền users:
Giả dụ có 2 user có uid là abcdef100abcdef101
Trong database ghi: /users/abcdef100/role = 100 và  /users/abcdef101/role = 10
Giờ bạn muốn user có role > 50 được phép đọc 1 chỗ trong database:
".read": "root.child('users').child(auth.uid).child('role').val() > 50"
Như vậy, chỉ có abcdef100 được quyền đọc

Dùng ngôn ngữ bolt để tạo và quản lý rules tốt hơn:
Việc sửa chay rules.json có thể sẽ rất rối mắt, mình khuyên dùng https://github.com/firebase/bolt để đơn giản hóa việc soạn rules. Ví dụ như hình sau thực ra được tạo ra từ bolt

 

MỚI! Đăng ký nhận bài viết mới nhất qua chatbot: