0
Blog

Sự lựa chọn đúng đắn về database trong Flutter

13/08/2020

Có rất nhiều tùy chọn hiện nay khi nói đến database trong Flutter. Có 3 loại phù hợp như sau:

Relational: đây là những cơ sở dữ liệu truyền thống. Không chỉ lưu trữ dữ liệu mà còn có mối liên quan giữa các dữ liệu. SQLite là một ví dụ về sự liên quan giữa các cơ sở dữ liệu.

NoSQL: lưu trữ dữ liệu dưới dạng tài liệu. Tốc độ nhanh và xử lý rất tốt các dữ liệu phi cấu trúc khổng lồ. MongoDB là một ví dụ về cơ sở dữ liệu NoSQL.

Lưu trữ dữ liệu được tùy chỉnh riêng: trong khi tùy chọn này không phải là cơ sở dữ liệu, bạn sẽ không thể nào sử dụng các giải pháp trên. Bạn có thể lưu trữ dữ liệu của mình trong file JSON đồng thời tự xử lý và mã hóa một cách tự động. Điều này sẽ giúp xử lý dữ liệu rất nhanh nhưng sẽ gặp một số lỗi khó hiểu nếu bạn không phải là Devloper chuyên nghiệp.

Trong bài viết này bạn sẽ biết được những điều sau :

  • Khái niệm cơ bản về cách hoạt động của các cơ sở dữ liệu.
  • Cách thiết lập với từng cơ sở dữ liệu.
  • Những trường hợp tốt nhất khi sử dụng với các cơ sở dữ liệu.
  • Trong bài viết này, chúng tôi sẽ chia các loại cơ sở dữ liệu theo ba loại được đề cập ở trên.

Relational

Mối quan hệ giữa các cơ sở dữ liệu đã có từ rất lâu (kể từ năm 1970, theo tìm kiếm trên Google). Hãy xem xét một số tùy chọn cho mối quan hệ cơ sở dữ liệu của Flutter.

SQflite

SQflite là 1 ứng dụng của SQLite  cho flutter. Nó cho phép bạn hoàn toàn kiểm soát database, queries, relationships và bất kể những gì bạn có thể nghĩ tới (đọc thêm tài liệu tại đây) :

// Get a location using getDatabasesPath
  var databasesPath = await getDatabasesPath();
  String path = join(databasesPath, 'demo.db');
 
  // Delete the database
  await deleteDatabase(path);
 
  // open the database
  Database database = await openDatabase(path, version: 1,
    onCreate: (Database db, int version) async {
   // When creating the db, create the table
   await db.execute(
     'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)');
  });Inserting data follows the same age-old SQLite tenants  // Insert some records in a transaction
  await database.transaction((txn) async {
   int id1 = await txn.rawInsert(
     'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');
   print('inserted1: $id1');
   int id2 = await txn.rawInsert(
     'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)',
     ['another name', 12345678, 3.1416]);
   print('inserted2: $id2');
  });

Và truy vấn như sau :

// Get the records
List<Map> list = await database.rawQuery('SELECT * FROM Test');

 

Ưu điểm :

  • Kiểm soát toàn bộ dữ liệu.
  • Cơ sở dữ liệu SQLite rất hiệu quả cho ứng dụng của bạn (hãy đảm bảo bạn đã có kiến thức về SQLite từ trước đó)
  • Dễ dàng sử dụng ứng dụng của bên thứ ba để đọc và mở nội dung của SQLite (vì vậy bạn có thể mở cơ sở dữ liệu trên máy tính khác và xem được nội dung bên trong như thế nào)

Nhược điểm :

  • Mất nhiều thời gian vì tất cả các truy vấn đều phải viết bằng tay.
  • Dữ liệu trả về từ Database không được nhập tốt ngay từ ban đầu.
  • Khó để xử lý việc thay đổi trên thiết bị (ví dụ: khi thay đổi dữ liệu giữa các bản cập nhật phiên bản)
  • Không có hỗ trợ cho web.

Use case

SQFlite tốt khi bạn cần relational dữ liệu nhưng cũng cần kiểm soát chi tiết đối với các truy vấn thực tế. Nếu bạn có thể thoải mái với việc viết các truy vấn lẫn code chuyển đổi của riêng mình thì điều này có thể phù hợp với bạn.

SQLite abstractions:

Sử dụng SQLite trực tiếp để quản lý cơ sở dữ liệu ứng dụng của bạn có thể mạnh mẽ nhưng cũng rất khó để sử dụng. Đó là lý do tại sao có rất nhiều giải pháp abstract một số chức năng từ SQLite để dễ sử dụng hơn trong khi vẫn giữ được rất nhiều lợi ích của SQLite.

FloorMoor là những ví dụ khá phổ biến về cách tiếp cận này. Trong bài viết này, MoorFloor là cách tiếp cận mà hai gói sử dụng trong SQLite abstract khá giống nhau.

Moor:

Để sử dụng Moor trong Flutter, bạn phải nhập gói Moor từ flutter pub, nhưng bạn có thể nhập moor_generator để thay thế. Điều này được build_runner sử dụng để tạo code cơ sở dữ liệu.

Tại sao chúng ta sử dụng build_runner ?

Build_runner chủ yếu được sử dụng để tạo code Flutter cho các dự án của bạn. Trước khi đến với Flutter, tôi hiếm khi phải sử dụng các tiện ích tạo code. Lý do chính cho điều này là vì hầu hết các ngôn ngữ lập trình khác mà tôi đã sử dụng cho đến thời điểm này (Ví dụ như C #) đều đã được hỗ trợ.

Nói một cách đơn giản, điều này giúp framework có thể gọi các phần của chương trình trong thời gian chạy một cách linh động. Nó khá mạnh, nhưng thường thì nó có thể khá chậm. Nó cũng ảnh hưởng đến các ứng dụng có liên quan, cũng như phản ánh, về mặt kỹ thuật, mọi phần ứng dụng của bạn đều có thể được truy cập hoặc sử dụng.

Chức năng của các gói thường được cung cấp bởi reflection, chúng thường sử dụng build_runner để tạo code cần trước thời gian. Điều này giúp thực thi code nhanh hơn và “tree sharking” tốt hơn hoặc giảm thiểu minimisation của ứng dụng tại thời điểm triển khai.

Tham khảo các tài liệu để hiểu cách tạo cơ sở dữ liệu.

import 'package:moor/moor.dart';// assuming that your file is called filename.dart. This will give an error at first,
// but it's needed for moor to know about the generated code
part 'filename.g.dart';// this will generate a table called "todos" for us. The rows of that table will
// be represented by a class called "Todo".
class Todos extends Table {
 IntColumn get id => integer().autoIncrement()();
 TextColumn get title => text().withLength(min: 6, max: 32)();
 TextColumn get content => text().named('body')();
 IntColumn get category => integer().nullable()();
}// This will make moor generate a class called "Category" to represent a row in this table.
// By default, "Categorie" would have been used because it strips away the trailing "s"
// in the table name.
@DataClassName("Category")
class Categories extends Table {
 
 IntColumn get id => integer().autoIncrement()();
 TextColumn get description => text()();
}// this annotation tells moor to prepare a database class that uses both tables
// we just defined. We'll see how to use that database class in a moment.
@UseMoor(tables: [Todos, Categories])
class MyDatabase {
}

Moor sẽ tạo lược đồ theo chương trình cho các tables của bạn, tùy thuộc vào cách bạn xác định nội dung cho chúng. Trong phần đầu của code example, chúng ta có thể thấy câu lệnh Part. Khi chạy lệnh build_runner, Moor tạo giản đồ dựa trên những gì đã xác định trong file. Bạn cũng có thể quay trở lại Raw SQL bất cứ lúc nào nếu bạn cần chạy một truy vấn cụ thể hoặc nếu muốn kiểm soát chi tiết hơn.

Ưu điểm :

  • Các kết quả xuất hiện mạnh mẽ
  • Dựa trên SQLite.
  • Bạn không cần tạo các truy vấn bằng cách thủ công bằng tay.
  • Mọi công việc nặng đều được xử lý bằng cách viết các dòng code.
  • SQLite Database có thể được điều hướng tới một loạt các công cụ hiện có để kiểm tra data trong quá trình phát triển.

Nhược điểm

  • Việc xử lý các bản cập nhật lược đồ trên các local device có thể rất phức tạp và cồng kềnh.
  • Việc hỗ trợ Web vẫn đang được xem xét.
  • Bị phụ thuộc vào nền tảng riêng (không được viết bằng ngôn ngữ Dart thuần túy).

Use case

Nếu bạn đang cần dữ liệu liên quan nhưng lại muốn viết càng ít SQL càng tốt. (Ví dụ: bạn đang quen với Entity Framework), thì đây có thể là lựa chọn phù hợp cho bạn.

 NoSQL

Có khá nhiều lựa chọn khi nói đến database NoSQL cho Flutter. FireBase là giải pháp rất nổi tiếng và lâu đời, song song đó có những giải pháp mới hơn như Hive. Có nhiều điểm khác biệt giữa Hive và FireBase, nhưng điểm khác biệt chính là FireBase có thể đồng bộ với Online Store, trong khi Hive phù hợp để lưu trức trên thiết bị cục bộ.

Firebase – NoSQL lưu trữ online

FireBase là một dạng database lưu trức theo cách truyền thống. Mọi lưu trữ data trong bộ sưu tập giống như bảng trong database truyền thống. Tài liệu được lưu trữ trong các bộ sưu tập này. Các kiểu  lưu trữ data như string, int…Chúng cũng có thể được liên kết đến những tài liệu khác. Mặc dù FireBase không liên kết hoàn toàn, nhưng bạn vẫn có thể tạo được sự liên kết với tài liệu của mình.

Quy trình thiết lập cho Firebase khá liên quan so với các tùy chọn trên thiết bị khác, như Moor hoặc Hive, nhưng bạn vẫn có được sự đồng bộ hóa dữ liệu giữa máy khách và máy chủ. Điều này có nghĩa là nếu bạn có nhiều khách hàng với một ứng dụng và tất cả họ đều tương tác với cùng một dữ liệu, thì dữ liệu này có thể được giữ đồng bộ giữa các khách hàng này. Ngoài ra, thiết lập này được trình bày khá tốt trong Google Codelab tại đây. Nhược điểm duy nhất của phương pháp này là bạn không nhận được lượng dữ liệu mạnh giống như cách bạn làm với Moor hoặc Hive. Bạn sẽ phải tự làm việc này bằng tay.

Ưu điểm :

  • Đồng bộ hóa với Firebase trực tuyến theo thời gian thực.
  • Hỗ trợ công cụ tuyệt vời.
  • Dễ dàng duyệt dữ liệu trực tuyến thông qua bảng điều khiển Firebase.

Nhược điểm :

  • Thiết lập Firebase có thể phức tạp nếu bạn đã thêm nó vào ứng dụng của mình.
  • Vì cơ sở dữ liệu đang trực tuyến, bạn cần chú ý nhiều hơn về cơ sở dữ liệu trên thiết bị (ví dụ như quyền truy cập).
  • FireBase chưa sẵn sàng để hỗ trợ cho Flutter

Use case

Nếu dữ liệu được truy cập giữa nhiều thiết bị và bạn muốn đồng bộ giữa các thiết bị được dễ dàng hơn, thì đây đây có thể là một giải pháp tốt..

 

Hive – NoSQL lưu trữ ngoại tuyến

Hive là một tùy chọn lưu trữ NoSQL rất nhanh cho các nhà phát triển Flutter. Điểm lớn nhất của nó là được phát triển từ Dart. Điều này có nghĩa là bất cứ nơi nào mà có Dart thì sẽ có Hive, vì nó không yêu cầu triển khai cho bất kỳ thiết bị nào.

Hive cho phép bạn lưu trữ dữ liệu dưới dạng HiveObject, cho phép các đối tượng có mối liên quan đến nhau. Nó lưu trữ các đối tượng trong một box, nhưng bạn có thể tạo TypeAdapters cho chúng.

Để tạo một box khá đơn giản:

var box = await Hive.openBox('testBox');

Dễ dàng đọc và viết:

import 'package:hive/hive.dart';void main() async {
 var box = await Hive.openBox('testBox'); box.put('name', 'David');
 
 print('Name: ${box.get('name')}');
}

Với khả năng tạo TypeAdapters thì Hive thực sự rất tốt (đọc thêm tài liệu tại đây). Điều này mang lại khả năng mạnh mẽ cho các Box của bạn, cho phép lưu trữ các lớp và cũng cho phép tham chiếu các đối tượng trong các Box khác.

Tài liệu hướng dẫn cách tạo một box cho riêng bạn, như dưới đây.

import 'package:hive/hive.dart';
part 'person.g.dart';
@HiveType()
class Person
{
@HiveField(0) String name;
@HiveField(1) int age;
@HiveField(2) List<Person> friends;
}

Như các bạn thấy, lớp này chứa list Person, vì vậy Hive có thể tham chiếu đến các đối tượng có trong danh sach.

Use case

Nếu bạn chỉ cần một cơ sở dữ liệu đơn giản để lưu trữ trên thiết bị của mình và không muốn đồng bộ hóa với Firebase hoặc bạn muốn thứ gì đó đó hoạt động ở mọi nơi, thì Hive sẽ là sự lựa chọn tốt dành cho bạn. Đó là cơ sở dữ liệu mà tôi sử dụng trong tất cả các ứng dụng của mình.

 

Tự tạo cho riêng bạn

Nếu bạn không bị ảnh hưởng bởi bất kỳ tùy chọn nào ở trên, bạn có thể tự tạo cơ sở dữ liệu cho riêng mình. Để làm được điều này bạn cần thông qua việc sử dụng thư viện như json_serializable và lưu trữ các tệp JSON trên thiết bị để ứng dụng truy cập.

Theo tôi, điều này giống như lắp chiếc xe từ đầu cho riêng bạn vậy. Tất nhiên, bạn có thể làm được và mọi người cũng vậy, nhưng tôi không chắc là sẽ thành công. Tạo một cơ sở dữ liệu mới có nghĩa là tạo một thư viện mới với các lỗi tiềm ẩn và rất nhiều vấn đề mới cần xem xét và suy nghĩ. Nếu bạn đã bắt đầu tạo ra một ứng dụng, việc tạo ra giải pháp database cho riêng bạn có thực sự nằm trong phạm vi thực hiện điều đó không ?

Các giải pháp hiện có như Hive hoặc Moor đã được sử dụng bởi rất nhiều người, tất cả đều tìm thấy lỗi và gửi các vấn đề trên lên GitHub. Đây là cách tốt nhất để mang lại chất lượng cho các thư viện này. Ai có thể thấy lỗi trên database của bạn? Bởi vì bạn là người duy nhất sử dụng nó, chỉ có bạn mới thấy được lỗi. Điều đó có vẻ không hay cho lắm phải không? Tôi hy vọng là không phải!

Use case

Nếu bạn hoàn toàn không tin tưởng vào tất cả các code không phải do bạn viết, bạn có thể cân nhắc việc tạo code cho riêng mình.

 

Quá nhiều lựa chọn!  Nên sử dụng cái nào?

Không có cách nào để trả lời database nào là ‘tốt nhất’ – nó phụ thuộc vào trường hợp mà bạn sử dụng. Nhưng có thể tóm tắt như sau:

  • Nếu dữ liệu của bạn là dạng relational và bạn muốn xem nó trên máy tính một cách dễ dàng trong quá trình phát triển và bạn không có ý định hỗ trợ web – bạn nên sử dụng Moor.
  • Nếu bạn cần dữ liệu được đồng bộ hóa giữa các thiết bị và bạn không cần thiết lập khóa mở rộng, bạn nên sử dụng Firebase.
  • Nếu bạn muốn khởi động nhanh và hỗ trợ cho web cùng với mọi thứ mà Dart chạy trên đó, thì bạn nên sử dụng Hive.
  • Nếu bạn nghi ngờ về tính bảo mật của các cơ sở dữ liệu này và không ai có thể thuyết phục bạn bằng cách khác, bạn có nhiều thời gian trong tay thì bạn có thể xem xét việc tạo database hoặc lưu trữ đối tượng của riêng mình bằng JSON.

Cảm ơn các bạn đã đọc bài viết về cơ sở dữ liệu cho Flutter. Tôi hy vọng bài viết này giúp công việc của bạn dễ dàng hơn.

Tham gia khóa học Flutter tại đây.