Data Model là gì? Như thế nào là một Data Model tốt?
Data Model là trái tim của mọi ứng dụng.
Suy cho cùng, tất cả đều hướng về dữ liệu: Dữ liệu đến từ bàn phím người dùng hoặc từ nguồn bên ngoài, dữ liệu được xử lý theo một số quy tắc nghiệp vụ và cuối cùng dữ liệu được hiển thị cho người dùng (hoặc các ứng dụng bên ngoài) theo một cách tiện lợi nhất.
Các khía cạnh của ứng dụng, hay chức năng bạn viết đều có dữ liệu liên quan để mang lại ý nghĩa cho hệ thống. Vì vậy, câu hỏi đặt ra ở đây là: Đâu là các yếu tố của một Data Model tốt? Câu trả lời sẽ được giải đáp trong bài viết dưới đây, nhưng trước tiên hãy đến với 2 định nghĩa:
Định nghĩa 1: Data Model là gì?
Data Model là một cách để tổ chức dữ liệu của ứng dụng. Bản thân Data Model không phải là dữ liệu, cũng không phải là thiết bị bạn sử dụng để lưu trữ dữ liệu (hệ thống cơ sở dữ liệu bạn chọn). Do đó có thể khẳng định như sau:
- Bạn có thể lưu trữ cùng một dữ liệu sử dụng các mô hình dữ liệu khác nhau.
- Bạn có thể lưu trữ các dữ liệu khác nhau bằng cách sử dụng cùng một mô hình dữ liệu.
- Có thể chuyển đổi dữ liệu từ mô hình dữ liệu này sang mô hình dữ liệu khác (quá trình này thường được gọi là Migration of Data – Chuyển giao dữ liệu).
Định nghĩa 2: Như thế nào là một Data Model tốt?
Nói cách khác là làm thế nào để có thể so sánh các tuỳ chọn mô hình dữ liệu khác nhau? Hay những khía cạnh nào cần được xem xét?
Có 5 khía cạnh liên quan đến một Data Model tốt:
- Tính rõ ràng: Sự dễ hiểu đối với những người sử dụng. Hầu hết thời gian developers đọc mã thay vì viết, vì vậy bạn cần hiểu rõ những gì đang làm với dữ liệu của mình.
- Tính linh hoạt: Khả năng phát triển của mô hình mà không cần tác động quá lớn đến các đoạn code. Công ty startup mà bạn làm việc đang phát triển, vì vậy các hệ thống sẽ thay đổi và các mô hình dữ liệu đằng sau chúng sẽ cần phải phát triển theo thời gian.
- Hiệu suất: Đây là một chủ đề rất rộng và bài viết này sẽ không nói về các nhà cung cấp cơ sở dữ liệu (database vendors) hoặc một số chỉnh sửa kỹ thuật để cải thiện tốc độ đọc và ghi dữ liệu. Cách thức thiết kế Data Model đúng đắn cũng đem lại lợi ích về hiệu suất. Phần sau sẽ đi sâu hơn vào khía cạnh này.
- Năng suất: Dưới góc nhìn của lập trình viên (developer), chắc hẳn bạn sẽ muốn có một mô hình dữ liệu dễ làm việc mà không cần sử dụng nhiều thời gian (định nghĩa về năng suất).
- Khả năng truy xuất nguồn gốc: Cuối cùng, các công ty không chỉ muốn có dữ liệu liên quan đến người dùng của mình, mà còn có dữ liệu liên quan đến chính hệ thống. Dữ liệu có thể cung cấp thông tin những gì đã xảy ra trong quá khứ, những giá trị công ty có tại một thời điểm nào đó.
Tựu trung, Data Model cần dễ hiểu, dễ mở rộng hoặc thay đổi, có hiệu suất tốt đồng thời tốt cho năng suất của nhà phát triển và có khả năng mang lại hiểu biết về những gì đã xảy ra trong quá khứ.
Các kỹ thuật lập Data Model
Thực tế, không có cách thức tuyệt đối nào để lập một mô hình dữ liệu hoàn hảo. Câu trả lời chính xác thường là “còn tuỳ vào nhiều yếu tố”, nhưng bài viết này sẽ đề xuất một cách tổng quát đáp ứng được hầu hết các yêu cầu đặt ra. Trước tiên, hãy tìm hiểu “cách thông thường” của việc lập mô hình dữ liệu mà chắc hẳn bạn sẽ cảm thấy quen thuộc.
Mô hình dữ liệu chuẩn (Domain Model)
Bạn xác định các đối tượng và thuộc tính của chúng dựa trên phạm vi của vấn đề đang giải quyết, giống như những loại hộp khác nhau sẽ dùng để cất giữ những loại đồ vật khác nhau.
Giả sử bạn đang phát triển giải pháp phần mềm Meetings. Phạm vi của bạn sẽ trông giống như danh sách sau:
- Cuộc họp: Với thông tin cơ bản về địa điểm, thời gian, thời lượng và hoạt động như nơi các thực thể còn lại được liệt kê bên dưới hoạt động
- Con người: Các thành viên của cuộc họp có thể với một số vai trò cụ thể (người tổ chức, thư ký, người thuyết trình...)
- Chủ đề: Chương trình cho cuộc họp dưới dạng danh sách các chủ đề với một số thứ tự, mô tả, thời lượng...
- Thoả thuận: Kết quả chính của cuộc trò chuyện có thể được gắn thẻ để thuận tiện tìm kiếm sau này
- Ghi chú: Các cuộc trò chuyện chính bên trong một chủ đề
- Hành động: Một số trách nhiệm ngắn hạn được giao cho một người.
Rõ ràng là loại mô hình này khá rõ ràng vì được định nghĩa giống như cách chúng ta nghĩ về vấn đề. Vì vậy, đầu tiên hãy thực hiện kiểm tra về 5 khía cạnh phân tích (thang điểm từ 1 đến 10)
- Sự thông suốt: 10 điểm. Có nghĩa là mô hình rất rõ ràng, giống như con người nghĩ.
- Tính linh hoạt: 3 điểm. Yếu tố không thật sự tốt vì với mỗi lĩnh vực mới được yêu cầu, sẽ cần một sự thay đổi về mô hình.
- Hiệu suất: 6 điểm. Loại mô hình này không có hiệu suất tốt nhất và lý do sẽ được trình bày sau đây.
- Năng suất: 3 điểm. Mỗi bộ sưu tập (hoặc bảng) sẽ cần phương thức riêng để cập nhật giá trị trong mỗi trường. Điều này không tốt cho năng suất của lập trình viên, trừ khi bạn phát triển một phần mềm trung gian để giao tiếp với cơ sở dữ liệu theo “cách tham số” nhưng điều này cũng không tự nhiên. ANATICS sẽ đề xuất một cách tốt hơn để thực hiện điều này.
- Khả năng truy xuất nguồn gốc: 2 điểm. Loại mô hình này cập nhật các trường dữ liệu ngay lập tức, vì vậy khi địa chỉ thay đổi, địa chỉ cũ sẽ bị mất. Cách giải quyết là có một bảng riêng biệt ghi lại tất cả các thay đổi (bảng nhật ký) nhưng sẽ được tách biệt với phần còn lại của mô hình.
Mô hình hoá tổng hợp
Cấu trúc của mô hình
Đề xuất chỉ có một bảng (hoặc bộ sưu tập) lưu trữ tất cả dữ liệu miền, theo cùng một cấu trúc và không làm mất bất kỳ dữ liệu nào (không cập nhật, không xoá). Có 2 ngoại lệ đối với quy tắc này, ANATICS sẽ đề cập đến chúng ở phần sau.
Cấu trúc của “universal record” này là:
- _id: Định danh duy nhất của thực thể.
- uniqueKey: (Tuỳ chọn) Đây cũng là một mã định danh duy nhất của thực thể nhưng được điều khiển bởi một số quy tắc kinh doanh. Ví dụ: địa chỉ email phải là duy nhất hoặc mối quan hệ giữa hai thực thể có thể tạo ra một thực thể mới có khoá duy nhất là “entity_id_1 - entity_id_2”.
- domain: Loại thông tin đang lưu trữ. Hầu hết tất cả đều hữu ích cho việc lọc tìm nạp dữ liệu và có tính rõ ràng cho nhà phát triển khi xem cơ sở dữ liệu.
- company_id: Trường này có thể gây tranh cãi, nhưng trong một số ứng dụng đã phát triển về khái niệm công ty (tổ chức mà người dùng của bạn thuộc về) luôn hiện hữu giúp tăng tính rõ ràng của mô hình dữ liệu khi có 1 trường dữ liệu chứa tất cả các domain liên quan.
- Mối quan hệ của dữ liệu: Bây giờ bạn suy nghĩ về các mối quan hệ. Hãy cho rằng có một bảng, không có quan hệ rõ ràng nào ở cấp mô hình (hoặc có ở cấp dữ liệu). Ở đây, bạn có thể xác định thực thể gốc của thực thể hiện tại, vì vậy khi có quyền truy cập vào thực thể gốc, bạn cũng sẽ có quyền truy cập vào thực thể này. Đây có thể là company_id hoặc user_id cho hầu hết các trường hợp.
- attrs: Đây là nơi chứa dữ liệu thực tế, là một mảng các đối tượng ở dạng {key, value, timestamp}.
Trường attrs
Với trường này, toàn bộ mô hình thực sự nằm trong trường attrs và mỗi khoá có thể nhiều hơn một lần (đối với các mốc thời gian “timestamp” khác nhau).
Ví dụ:
- {key: ‘name’, value: ‘José’,timestamp: 1575490495682}.
- {key: ‘name’, value: ‘José Manuel’,timestamp: 1575490495795}.
Cho biết rằng cùng một tên khoá có giá trị 'José' tại timestamp 1575490495682, nhưng sau đó đã đổi thành 'José Manuel' tại 1575490495795. Do dấu thời gian này lớn hơn dấu thời gian trước đó, ANATICS xem giá trị này là giá trị hiện tại.
Ngoài ra, sẽ luôn có 3 trường đặc biệt bên trong trường attrs:
- company_id: Đã được giải thích.
- user_id: Hoặc người dùng chịu trách nhiệm về việc tạo thực thể.
- trạng thái: Giá trị 1 cho các thực thể đang hoạt động và -1 cho những gì đã xoá (mặc dù thực sự không bao giờ xoá một thực thể)
Lưu ý rằng “hình dạng” của thuộc tính giá trị bên trong mỗi thuộc tính có thể thuộc bất kỳ kiểu nào. Nếu nghĩ theo thuật ngữ Javascript, bạn có thể có: Chuỗi, Booleans, Số, Ngày, Mảng, Đối tượng...
Bây giờ, đã đến lúc xem chi tiết từng khía cạnh trong phân tích 5 khía cạnh của Data Model.
Sự thông suốt
Đây không phải là tính năng lớn nhất của mô hình này, bởi vì mỗi khi nhìn vào một bản ghi, bạn cần phải đi sâu vào bên trong trường attrs. Đây là chi phí phải trả, sự đánh đổi để có được những lợi ích khác.
Mặc dù sau khi làm việc với mô hình giá trị quan trọng này một thời gian, bạn sẽ “thấy mô hình rất rõ ràng”, nhưng đối với người đọc, thoạt nghe có vẻ khó hiểu.
Bạn sẽ nhận một phần thưởng lớn khi lập mô hình như thế này bằng cách mô tả tất cả mô hình của mình bằng một câu truy vấn. Vì vậy, nếu cần có một tài liệu (hoặc tốt hơn là một trang web) hiển thị các trường “thực” của mỗi thực thể, chúng ta có thể đạt được điều đó rất dễ dàng.
Linh hoạt
Tính linh hoạt được tích hợp trong mô hình meta và đó là khái niệm cốt lõi. Thay vì xác định trước các trường của thực thể cho mỗi phạm vi (còn được gọi là “lược đồ”), ANATICS chỉ xác định cấu trúc chung này có thể chứa bất kỳ lược đồ nào.
Tính linh hoạt thực sự mạnh mẽ trong trường hợp này:
- Hệ thống của bạn cần lưu trữ loại dữ liệu mới tại một thực thể nhất định hoặc có thể là các thực thể mới. Bạn đã biết rằng bất kỳ thực thể nào cũng có thể được mô hình hoá bằng định nghĩa khoá giá trị (Key-value) đơn giản, vì vậy bạn sẽ không phá vỡ bất kỳ đoạn mã nào bằng cách thực hiện thay đổi này. Vấn đề là mô hình thực sự nằm bên trong dữ liệu, không phải bên trong cơ sở dữ liệu.
- Bạn cần thay đổi mối quan hệ giữa các thực thể, có thể có một thực thể phụ thuộc vào người dùng, và bây giờ cần phụ thuộc vào một nhóm người dùng… Đừng lo lắng, bạn chỉ cần cập nhật trường mối quan hệ bằng một truy vấn. Trong trường hợp này, có thể bạn sẽ cần thay đổi mã, nhưng bạn (một lần nữa) không cần phải thay đổi mô hình.
Hiệu suất
Hiệu suất là một lợi ích ít rõ ràng nhất của loại mô hình này. Bạn có thể lập luận rằng mô hình này chiếm nhiều không gian hơn mô hình truyền thống. Nhưng ngày nay việc lưu trữ không phải là một vấn đề vì có nhiều cách thức thực hiện khác nhau với chi phí thấp.
Hiệu suất chính không liên quan đến cách lưu trữ khoá giá trị, nhưng với trường dấu thời gian kết hợp với trường không bao giờ cập nhật hoặc xoá bất kỳ thứ gì.
Do đó, khi khách hàng đọc từ mô hình này (nơi có thể cảm nhận được hiệu suất mô hình) không cần phải lấy tất cả các thực thể, cũng như tất cả các trường thực thể mà họ quan tâm, bởi vì họ có thể đã có sẵn thông tin.
Ví dụ:
- Một số dữ liệu đã được tạo trong cơ sở dữ liệu tại dấu thời gian t_0.
- Một khách hàng xem ứng dụng yêu cầu một số dữ liệu tại t_1 và máy chủ trả lời bằng dữ liệu mà khách hàng quan tâm (chỉ dữ liệu mà module yêu cầu/có quyền truy cập). Sau đó, khách hàng đăng xuất ứng dụng tại dấu thời gian t_1.
- Tiếp đến, tại dấu thời gian t_2 và t_3, máy chủ nhận dữ liệu mới, cung cấp thông tin đến cho những người dùng khác đã tương tác với ứng dụng.
- Và sau đó, tại t_4, khách hàng kết nối lại và thay vì xem lại tất cả thông tin (cộng thêm thông tin mới), khách hàng chỉ nhận thông tin mới cần thiết, tránh lãng phí khi chuyển lại dữ liệu khách hàng đã có .
Quá trình này khá tốt cho hiệu suất. Thay vì điều chỉnh một số chi tiết nhỏ, ANATICS lược bỏ rất nhiều công việc mỗi khi truy cập cơ sở dữ liệu bằng cách truy vấn: Chỉ cần cung cấp những dữ liệu từ mốc thời gian này về trước.
Điều này không chỉ hoạt động ở cấp thực thể (các thực thể mới sẽ được gửi) mà còn ở cấp trường (chỉ gửi trường mới của các thực thể cũ), giảm dung lượng cần trao đổi về lâu dài.
Nói cách khác, ANATICS đang triển khai một bộ nhớ cache cục bộ, điều đó có thể xảy ra chỉ vì không cập nhật các trường, mà chỉ hướng đến việc bổ sung dữ liệu mới.
Trong trường hợp nào phá vỡ quy tắc “Chỉ thêm vào, không xoá hay cập nhật dữ liệu”?
Có 2 tình huống cần xem xét, đề phòng trường hợp bạn băn khoăn không biết làm thế nào để quản lý:
- Trường chỉ là trạng thái, có tính biến động. Giả sử bạn đã gửi thông báo cho người dùng. Họ có thể muốn đánh dấu là đã đọc hoặc chưa đọc và có thể thực hiện quá trình này nhiều lần. Vì vậy, không có giá trị thực nào giữ tất cả “lịch sử nhấp chuột”, vì bản chất của dữ liệu.
- Trường đại diện cho một thực thể con, ví dụ: bạn có thể có một domian gọi là “Kỹ năng” có “điểm” từ 1 đến n. Bạn có thể tạo một domain mới có tên “SkillGrades” nhưng là một thực thể con sẽ đơn giản hơn nếu được lồng vào bên trong domian gốc. Vấn đề là thay vì có nhiều khoá = ‘điểm’ cho mỗi lần bạn thêm hoặc xoá điểm, bạn chỉ có một khoá = ‘điểm’ và bạn thêm vào bên trong.
Trong cả hai trường hợp, ANATICS cập nhật timestamp của các trường này trong mỗi lần cập nhật. Vì vậy, timestamp sẽ được công nhận là thông tin mới vào lần tiếp theo khách hàng yêu cầu.
Năng suất
Nếu bạn có 20-30 loại thực thể trong mô hình của mình (và không quá khó để đạt được con số này), bạn cần phải có một số phương thức CRUD (Tạo – Đọc – Cập nhật – Xoá) cho từng thực thể. Vì vậy, bạn sẽ có khoảng 100 phương pháp được sử dụng trong từng trường hợp.
Sao chép mã hiện có từ một phương thức, thay đổi tên tệp, thay đổi nội dung của phương thức, thêm/xoá trường... Mỗi khi bạn thay đổi các trường của thực thể thì sẽ xảy ra điều gì? Định vị tệp, thay đổi trường, khởi động lại máy chủ…
Khi bạn có một mô hình meta, bạn sẽ nhanh chóng nhận ra rằng bạn cần phải xây dựng một số chức năng tiện ích để tương tác. Tuy nhiên sự khác biệt với 100 phương thức được đề cập ở trên là tập hợp các tiện ích nhỏ hơn và không phụ thuộc vào quy mô domain của bạn. Dưới đây là một số chức năng nên xem xét:
- createEntity: Tạo cấu trúc cơ bản và đó là thực thể không tính đến đúng sai (giống như tất cả các chức năng khác bên dưới).
- addAttrsToEntity: Chức năng “cập nhật”, cho phép thêm dữ liệu mới vào thực thể mà không làm mất dữ liệu trước đó.
- addParentKey: Thêm “thông tin quan hệ” của thực thể, để có thể xác định sau này ai có thể truy cập vào các dữ liệu này.
- getEntities: Bằng _id hoặc uniqueKey, bạn nhận được dữ liệu thực thể.
- pickKeyCurrentValue: Hầu hết thời gian nhà lập trình chỉ quan tâm đến giá trị cuối cùng (hiện tại) của mỗi trường, vì vậy bạn chuyển một thực thể cho phương thức này và một số khoá để trả về và nhận được một đối tượng có giá trị cuối cùng cho mỗi khoá.
- getRelatedEntities: Cho phép trả về các thực thể ở cấp công ty (tất cả người dùng đều có quyền truy cập) ở cấp người dùng hoặc các cấp khác (ví dụ: route, tham số truy vấn).
Bạn sẽ mong đợi có ít hơn khoảng 50% đến 70% khối lượng mã cần được viết, nhưng cũng ít lỗi hơn do đã được tiêu chuẩn hoá.
Truy xuất nguồn gốc
Nếu xoá dữ liệu (vật lý) hoặc cập nhật dữ liệu tại chỗ (cách tiếp cận phổ biến nhất), bạn đang mất khả năng biết những gì đã xảy ra trong quá khứ. Đôi khi đây là những gì bạn thực sự mong muốn (ví dụ trong 2 trường hợp đã nêu ở trên) nhưng những lúc khác bạn chỉ cảm thấy an toàn hơn.
Bạn biết rằng cơ sở dữ liệu không chỉ lưu giữ dữ liệu có liên quan đến người dùng mà còn lưu giữ các sự kiện liên quan đến từng phần dữ liệu. Điều này tốt cho các công việc sau này như:
- Gỡ lỗi (debug): Bạn có thể “thực sự thấy những gì đã xảy ra”.
- Phân tích: Bạn cũng có khái niệm về tình trạng hoạt động của ứng dụng đang xây dựng bằng cách xem cơ sở dữ liệu.
Tiếp theo, hãy xem xét các con số với cách tiếp cận mới:
Meta-Model có những lợi thế rõ ràng trong mỗi chiều, ngoại trừ việc ít rõ ràng hơn (mặc dù bạn có thể điều chỉnh ý định của mình theo mô hình này theo thời gian). Lấy hình ảnh chỉ để so sánh 2 phương án, các con số là tuỳ ý và không dựa trên các nghiên cứu nghiêm túc.
Vậy điều gì liên quan đến việc sử dụng mô hình này trong Browser?
Đây có thể là “phần 2” của bài viết này, nhưng tóm lại, các khía cạnh chính là:
- Có một bản sao cục bộ của dữ liệu nhận được từ máy chủ tại LocalStorage (hoặc thậm chí tốt hơn trong IndexedDB). Dữ liệu này chỉ được tận dụng khi có dữ liệu mới.
- Sau đó, ANATICS sẽ điền vào một đối tượng (một đối tượng JS lớn) với tất cả dữ liệu với 2 phép biến đổi:
- ANATICS sẽ chỉ có giá trị cuối cùng của mỗi trường (dấu thời gian sẽ không còn cần thiết)
- Thay vì có dữ liệu ở dạng [{key: 'name', value: 'José}, {key:' city ', value:' Santiago '}], bạn sẽ có dữ liệu giống như {name:' José ', thành phố:' Santiago '}. Vì vậy, dữ liệu sẽ tự nhiên hơn đối với mô hình tư duy.
Điều này rất quan trọng, bởi vì bất kỳ ai cũng đều đang quan tâm đến “khả năng truy cập” vào đoạn mã mà khách hàng cũng có. Khi dữ liệu mới được tạo ra bởi người dùng, bạn sẽ thêm dữ liệu vào đối tượng JS lớn của mình và cũng thực hiện yêu cầu đến máy chủ, vì vậy giao diện người dùng sẽ phản ánh các thay đổi ngay lập tức.
Cuối cùng nhưng không kém phần quan trọng, việc xây dựng Module quản trị qua Mô hình Meta (Tạo – Xoá – Cập nhật dữ liệu mới) cũng rất dễ thực hiện. Bạn sẽ dùng ít đoạn mã đặc biệt hơn cho mỗi bộ sưu tập, các đoạn mã bạn viết sẽ có tính nhất quán hơn và chỉ cần ít thành phần UI để viết code.
* Nguồn: ANATICS Tech & Data Consultancy