Để hoàn thành dự án “Notopia - Ứng dụng ghi chú thông minh hỗ trợ quản lý tri thức bằng biểu đồ quan hệ”, bên cạnh sự nỗ lực của các thành viên, chúng em đã may mắn nhận được sự đồng hành và chỉ dẫn vô cùng quý giá.
Đặc biệt, chúng em xin bày tỏ lòng tri ân sâu sắc nhất tới cô Trần Thị Hồng Yến. Không chỉ là người định hướng chuyên môn, cô còn dành tâm huyết để khích lệ, giúp chúng em tháo gỡ những nút thắt kỹ thuật và tư duy hệ thống trong suốt quá trình nghiên cứu. Những góp ý đầy tận tâm của cô chính là kim chỉ nam giúp Notopia hình thành và hoàn thiện như ngày hôm nay.
Đồng thời, nhóm xin gửi lời cảm ơn chân thành đến Khoa Công nghệ Phần mềm, trường Đại học Công nghệ Thông tin – ĐHQG TP.HCM. Cảm ơn Nhà trường đã tạo dựng một môi trường học tập hiện đại, cung cấp nền tảng tri thức và cơ sở vật chất tốt nhất để chúng em thực hiện đề tài này.
Mặc dù đã dồn hết tâm sức, song đồ án chắc chắn vẫn còn những điểm cần cải thiện. Chúng em rất mong nhận được sự chỉ dẫn và đóng góp từ quý thầy cô để nhóm có thể phát triển ứng dụng ngày một hoàn thiện hơn.
| STT | Ký hiệu | Ý nghĩa |
| 1 | ABAC | Attribute-Based Access Control |
| 2 | ACL | Access Control List |
| 3 | CI/CD | Continuous Integration/Continuous Deployment |
| 4 | CLI | Command-Line Interface |
| 5 | CRDT | Conflict-free Replicated Data Type |
| 6 | DX | Developer Experience |
| 7 | HTTP/2 | Hypertext Transfer Protocol Version 2 |
| 8 | ISR | Incremental Static Regeneration |
| 9 | JSX | JavaScript XML Syntax Extension |
| 10 | OAuth2 | OAuth 2.0 Authorization Framework |
| 11 | OIDC | OpenID Connect |
| 12 | ORM | Object-Relational Mapping |
| 13 | OTLP | OpenTelemetry Protocol |
| 14 | RBAC | Role-Based Access Control |
| 15 | REST | Representational State Transfer |
| 16 | RPC | Remote Procedure Call |
| 17 | SSE | Server-Sent Events |
| 18 | SSG | Static Site Generation |
| 19 | SSO | Single Sign-On |
| 20 | SSR | Server-Side Rendering |
| 21 | gRPC | Google Remote Procedure Call |
Đề tài tập trung nghiên cứu và xây dựng “Notopia - Ứng dụng ghi chú thông minh hỗ trợ quản lý tri thức bằng biểu đồ quan hệ”, với mục đích giúp người dùng quản lý tri thức cá nhân trên một nền tảng web trực quan, cộng tác theo thời gian thực. Đề tài không hướng đến việc giải quyết các nhược điểm của các nền tảng sẵn có trên thị trường, mà thay vào đó tập trung nghiên cứu kiến trúc, phương pháp phát triển phần mềm, triển khai hệ thống, khai thác và sử dụng các công nghệ hiện đại.
Báo cáo trình bày các nghiên cứu, quy trình thiết kế, cài đặt và triển khai hệ thống thông qua các chương sau:
Chương này trình bày bối cảnh thực tiễn và lý do lựa chọn đề tài, từ đó làm rõ mục tiêu, phạm vi và đối tượng nghiên cứu của dự án. Nội dung của chương nhằm giúp người đọc có cái nhìn toàn diện về định hướng nghiên cứu, cơ sở khoa học và giá trị ứng dụng thực tiễn của đề tài trước khi đi sâu vào các chương phân tích và thiết kế chi tiết ở các phần tiếp theo.
Trong kỷ nguyên bùng nổ thông tin, việc quản lý kiến thức cá nhân (Personal Knowledge Management - PKM) trở thành một kỹ năng thiết yếu. Các phương pháp ghi chú truyền thống theo dạng danh sách hoặc thư mục dần bộc lộ hạn chế trong việc kết nối các ý tưởng rời rạc. Lấy cảm hứng từ các ứng dụng như Notion, Obsidian, chúng em xây dựng nền tảng ghi chú hiện đại hỗ trợ liên kết hai chiều1, kết hợp trực quan hóa kiến thức thành biểu đồ quan hệ.
Xây dựng nền tảng web quản lý tri thức cá nhân, trực quan hoá bằng biểu đồ quan hệ (Graph View), cộng tác theo thời gian thực. Ứng dụng cung cấp các chức năng ghi chú cơ bản: tạo, sửa, xoá tạm thời, xoá vĩnh viễn, khôi phục, tìm kiếm toàn văn (full-text search) và phân quyền truy cập không gian làm việc (workspace).
Đối tượng nghiên cứu về mặt nghiệp vụ
Đối tượng nghiên cứu về mặt kỹ thuật
Sử dụng dịch vụ thứ bên thứ 3 để quản lý đăng ký, đăng nhập, thông qua OAuth2/OpenID Connect, đảm bảo an toàn và dễ dàng tích hợp với các dịch vụ khác trong hệ thống.
Quản lý không gian làm việc, sắp xếp các ghi chú theo cấu trúc thư mục, biểu diễn quan hệ giữa các ghi chú, hỗ trợ cộng tác theo thời gian thực ở góc độ lưu trữ và tổ chức thông tin.
Nội dung ghi chú được lưu trữ dưới dạng tài liệu Block-based (BlockNote), cho phép trình bày và chỉnh sửa linh hoạt, đồng thời hỗ trợ cộng tác thời gian thực. Tệp tin đính kèm được lưu qua Object Storage.
Quản lý quyền truy cập vào không gian làm việc. Không gian làm việc có thể chia sẻ với nhiều người dùng với quyền hạn khác nhau, đảm bảo an toàn và kiểm soát truy cập.
Lắng nghe và đồng bộ nội dung ghi chú khi thay đổi đến dịch vụ tìm kiếm bên thứ ba.
Dự án áp dụng phương pháp tiếp cận kỹ thuật hệ thống, kết hợp nghiên cứu lý thuyết quản lý tri thức và triển khai thực nghiệm công nghệ phần mềm.
Khảo sát các ứng dụng quản lý ghi chú hiện có (Notion, Obsidian, jackyzha0/quartz) để rút ra các tính năng cần thiết.
Sử dụng UML cho sơ đồ lớp (class diagram) và sơ đồ tuần tự (sequence diagram). Dùng D2 cho sơ đồ triển khai (deployment diagram) và cơ sở dữ liệu quan hệ. Trong đó:
Thiết kế API theo chuẩn RESTful, sử dụng OpenAPI 3.0 để mô tả API giữa Web App và API service, Protocol Buffers 3 cho giao tiếp giữa các service nội bộ. Áp dụng hướng tiếp cận “Contract First”, đảm bảo tính nhất quán từ giai đoạn thiết kế đến triển khai.
Nhờ đặc tả này, có thể tự động sinh mã nguồn client và server, giảm lỗi và tăng tốc phát triển.
Notopia là nền tảng quản lý tri thức cá nhân, hỗ trợ người dùng tổ chức, kết nối và trực quan hóa kiến thức trên web với khả năng cộng tác thời gian thực. Các nhóm chức năng chính bao gồm:
Tạo và quản lý không gian làm việc
Người dùng có thể tạo các không gian làm việc riêng biệt, chia sẻ và quản lý quyền truy cập linh hoạt. Mỗi không gian là vùng lưu trữ độc lập cho ghi chú và dự án.
Tổ chức ghi chú theo cấu trúc thư mục
Người dùng sắp xếp ghi chú thành thư mục lồng nhau, tạo cấu trúc dữ liệu rõ ràng, giúp quản lý số lượng lớn ghi chú hiệu quả.
Tạo các liên kết hai chiều giữa ghi chú
Người dùng liên kết ghi chú qua cơ chế liên kết hai chiều, tạo mạng lưới tri thức động. Hệ thống tự động ghi nhận backlink, giúp hiểu rõ mối quan hệ giữa các ý tưởng.
Soạn thảo nội dung dạng block-based
Nội dung ghi chú được tổ chức thành các khối độc lập, cho phép linh hoạt xây dựng và chỉnh sửa tài liệu. Mỗi khối có thể là văn bản, hình ảnh, mã code hoặc loại nội dung khác.
Cộng tác theo thời gian thực
Nhiều người dùng có thể làm việc đồng thời trên cùng ghi chú. Hệ thống đồng bộ thay đổi tức thời và giải quyết xung đột tự động.
Trực quan hóa mối quan hệ bằng biểu đồ quan hệ
Người dùng xem mạng lưới tri thức dưới dạng biểu đồ quan hệ (Graph View), hiển thị liên kết và mối quan hệ giữa các ghi chú, trực quan hơn cách tổ chức tuyến tính truyền thống.
Tìm kiếm nhanh chóng
Hệ thống hỗ trợ tìm kiếm toàn văn trên mọi nội dung ghi chú, giúp người dùng nhanh chóng tìm ra thông tin cần thiết.
Quản lý quyền truy cập và phân quyền
Chủ sở hữu không gian làm việc quản lý quyền từng thành viên (xem, chỉnh sửa, xóa) với nhiều cấp độ quyền, đảm bảo an toàn và kiểm soát truy cập.
Quản lý vòng đời tài liệu
Ghi chú có thể xóa tạm thời (vào thùng rác) hoặc xóa vĩnh viễn. Người dùng có thể khôi phục ghi chú đã xóa tạm thời, tránh mất dữ liệu.
Notopia ứng dụng bộ công nghệ được lựa chọn kỹ lưỡng, đảm bảo mở rộng, hiệu suất cao và bảo trì dài hạn.
Web App
Backend
document service: NestJS (TypeScript), kiến trúc mô-đun, build với Rspack.notes service: Go với SQLC cho raw SQL tối ưu, phù hợp truy vấn đồ thị phức tạp.authorization service: Go với Casbin (RBAC) cho quản lý quyền linh hoạt.search-worker: NestJS lắng nghe thay đổi dữ liệu và đồng bộ với Meilisearch.Cơ sở dữ liệu
Cộng tác theo thời gian thực
Giao tiếp API
Bên cạnh đó, các dịch vụ không hỗ trợ gRPC có SDK hoặc REST API (ví dụ Authentik có SDK cho Go và NodeJS).
Kiến trúc sự kiện
NestJS hỗ trợ sẵn event-driven architecture, không cần thư viện ngoài.
Giám sát
Hạ tầng
Go (còn gọi là Golang) là ngôn ngữ lập trình mã nguồn mở được phát triển bởi Google vào năm 2007 và chính thức phát hành vào năm 2009. Được thiết kế bởi Robert Griesemer, Rob Pike và Ken Thompson, Go nhằm mục tiêu tạo ra một ngôn ngữ hiệu quả, dễ học, và phù hợp cho lập trình hệ thống quy mô lớn.
Dự án sử dụng Go cùng với công cụ goforj/wire, một fork của google/wire, giúp dependencies injection có hỗ trợ cache fast để tối ưu thời gian build.
Go mang lại nhiều lợi ích trong phát triển backend:
Bên cạnh các ưu điểm, Go có một số hạn chế:
if err != nilTypeScript là một ngôn ngữ lập trình mã nguồn mở được phát triển và duy trì bởi Microsoft. TypeScript là một superset của JavaScript, nghĩa là mọi code JavaScript hợp lệ đều là code TypeScript hợp lệ. Được ra mắt lần đầu vào năm 2012 bởi Anders Hejlsberg (người thiết kế C#), TypeScript bổ sung hệ thống kiểu dữ liệu tĩnh trên nền tảng JavaScript để tăng độ tin cậy và khả năng duy trì của code.
TypeScript hoạt động thông qua một bước biên dịch: mã TypeScript được biên dịch thành mã JavaScript, sau đó được chạy trên JavaScript runtime (trình duyệt hoặc Node.js).
TypeScript mang lại nhiều lợi ích khi phát triển ứng dụng JavaScript:
Bên cạnh các ưu điểm, TypeScript có một số hạn chế:
Dự án microsoft/typescript-go đang phát triển một implementation của TypeScript được viết hoàn toàn bằng Go, thay vì TypeScript hiện tại được viết bằng TypeScript. Dự án này hướng tới việc cải thiện hiệu suất.
Dự án được thiết lập với monorepo, các package được chia build riêng biệt (SWC, Rspack, tsgo, vite) để tăng tốc thời gian build, Oxlint thay cho ESLint, Oxfmt thay cho Prettier để tăng tốc độ linting và formatting, CI, ngoại trừ web vì sử dụng NextJS.
Frontend của dự án được xây dựng với React, một thư viện JavaScript cho xây dựng user interfaces với component-based architecture. Next.js được sử dụng như một framework trên React, cung cấp server-side rendering, static generation, và routing tích hợp.
React là thư viện JavaScript mã nguồn mở từ Meta (Facebook) cho xây dựng user interfaces. React sử dụng virtual DOM để tối ưu rendering, component-based architecture cho reusability, và declarative syntax làm cho code dễ đọc hơn.
Next.js là framework React được phát triển bởi Vercel, cung cấp Server-Side Rendering (SSR), Static Site Generation (SSG), Incremental Static Regeneration (ISR), và API routes tích hợp. Next.js giúp tối ưu hiệu suất và SEO mà không cần setup phức tạp.
React và NextJS mang lại nhiều lợi ích cho phát triển frontend:
Bên cạnh các ưu điểm, các công nghệ frontend này có một số hạn chế:
NestJS là một framework progressive Node.js được xây dựng để phát triển các ứng dụng server-side hiệu quả, đáng tin cậy và có khả năng mở rộng cao. Được phát triển bởi Kamil Myśliwiec và ra mắt lần đầu vào năm 2017, NestJS kết hợp các khái niệm từ Angular, Spring Framework và các framework hiện đại khác.
NestJS được xây dựng trên nền tảng Express.js (hoặc Fastify) và sử dụng TypeScript làm ngôn ngữ chính. Framework này tổ chức code theo mô hình kiến trúc mô-đun rõ ràng, bao gồm controllers, services, middleware, guards, interceptors, và pipes, tương tự như Spring Framework của Java.
Trong dự án này, NestJS được xây dựng với:
NestJS mang lại nhiều lợi ích cho phát triển backend:
Bên cạnh các ưu điểm, NestJS có một số hạn chế:
PostgreSQL (còn gọi là Postgres) là một hệ quản trị cơ sở dữ liệu quan hệ đối tượng (ORDBMS) mã nguồn mở, mạnh mẽ và tiên tiến. PostgreSQL được phát triển từ dự án POSTGRES tại Đại học California, Berkeley vào năm 1986 và đã phát triển hơn 35 năm với cộng đồng đóng góp tích cực.
PostgreSQL nổi tiếng vì sự tuân thủ nghiêm ngặt các chuẩn SQL, hỗ trợ ACID transactions đầy đủ, và khả năng mở rộng cao thông qua các kiểu dữ liệu tùy chỉnh và extensions như PostGIS, pgcrypto, và full-text search.
PostgreSQL mang lại nhiều lợi ích cho phát triển ứng dụng:
Bên cạnh các ưu điểm, PostgreSQL có một số hạn chế:
Trong các ứng dụng cho phép nhiều người dùng cùng chỉnh sửa một tài liệu đồng thời, vấn đề cốt lõi là làm thế nào để đảm bảo tính nhất quán dữ liệu khi các thay đổi xảy ra song song từ nhiều nguồn khác nhau. Đây là bài toán đã được nghiên cứu sâu rộng trong lĩnh vực hệ thống phân tán và là nền tảng cho mọi công cụ cộng tác thời gian thực hiện đại.
Hiện nay có hai trường phái giải pháp được nghiên cứu và ứng dụng phổ biến nhất: Operational Transformation (OT) và Conflict-free Replicated Data Type (CRDT). Mỗi phương pháp tiếp cận bài toán theo một hướng khác nhau, với những đánh đổi riêng về độ phức tạp, hiệu suất và khả năng mở rộng.
Lịch sử
Operational Transformation được giới thiệu lần đầu vào năm 1989 bởi C. Ellis và S. Gibbs trong bài báo “Concurrency Control in Groupware Systems”. Bước ngoặt lớn đến vào năm 2009, khi Google công bố Google Wave và Google Docs sử dụng OT ở quy mô hàng triệu người dùng, đưa công nghệ này từ lý thuyết vào sản phẩm thương mại đại trà.
Nguyên lý hoạt động
Operational Transformation giải quyết xung đột bằng cách biến đổi các thao tác chỉnh sửa trước khi áp dụng chúng, sao cho kết quả cuối cùng luôn nhất quán bất kể thứ tự nhận được.
Khi người dùng A thực hiện thao tác X và người dùng B đồng thời thực hiện thao tác Y, hệ thống OT sẽ tính toán một phiên bản biến đổi X’ sao cho việc áp dụng X’ sau Y cho ra kết quả tương đương với việc áp dụng Y’ sau X. Quá trình biến đổi này thường được điều phối qua một máy chủ trung tâm đóng vai trò trọng tài, đảm bảo thứ tự các thao tác được xác định nhất quán trên tất cả các phiên làm việc.
Quay lại ví dụ trên, tài liệu đang có nội dung “Hello”. Người dùng A xóa ký tự tại vị trí 0, người dùng B chèn “!” vào vị trí 5. Khi máy chủ nhận được cả hai thao tác, nó nhận ra rằng xóa ký tự đầu đã thay đổi toàn bộ chỉ số vị trí, do đó sẽ biến đổi thao tác của B thành “chèn ‘!’ vào vị trí 4”. Kết quả cuối cùng trên cả hai phía là “ello!”, nhất quán và đúng đắn.
Ưu điểm
Nhược điểm
Lịch sử
Conflict-free Replicated Data Type được định nghĩa chính thức vào năm 2011 bởi Marc Shapiro, Nuno Preguiça, Carlos Baquero và Marek Zawirski tại INRIA. Ý tưởng khởi nguồn từ nhu cầu điện toán phân tán và di động, nơi kết nối mạng không ổn định. Khác với OT, CRDT thiết kế cấu trúc dữ liệu sao cho mọi cách kết hợp thay đổi đều cho ra cùng một kết quả, đảm bảo về mặt toán học thay vì thông qua điều phối tập trung.
Nguyên lý hoạt động
CRDT giải quyết bài toán xung đột theo hướng hoàn toàn khác: thay vì biến đổi thao tác sau khi xung đột xảy ra, CRDT thiết kế cấu trúc dữ liệu sao cho xung đột không bao giờ thực sự tồn tại.
Nguyên tắc cốt lõi là mỗi phần tử dữ liệu được gán một định danh duy nhất và bất biến, thường kết hợp giữa mã định danh của thiết bị và dấu thời gian logic (logical clock). Khi hai người dùng chèn nội dung vào cùng một vị trí, thay vì phải quyết định ai ưu tiên hơn, hệ thống sử dụng thứ tự toàn phần dựa trên các định danh này để sắp xếp chúng theo một thứ tự xác định và có thể tái lập lại trên mọi thiết bị. Kết quả là tất cả các bản sao sẽ tự động hội tụ về cùng trạng thái mà không cần bất kỳ sự điều phối trung tâm nào.
Có hai nhánh chính của CRDT:
Ưu điểm
Nhược điểm
| Tiêu chí | OT | CRDT |
|---|---|---|
| Kiến trúc | Tập trung, máy chủ giữ thẩm quyền | Phi tập trung, hỗ trợ ngang hàng |
| Mô hình nhất quán | Mạnh (thứ tự do máy chủ quyết định) | Tối thượng (ngữ nghĩa hợp nhất) |
| Giải quyết xung đột | Biến đổi tất định qua máy chủ | Quy tắc hợp nhất của CRDT |
| Chi phí bộ nhớ | Số hiệu phiên bản và trạng thái hiện tại | Nhãn xóa mềm, định danh duy nhất và đồng hồ vector cho mỗi phần tử |
| Hỗ trợ ngoại tuyến | Hạn chế, đệm thao tác và phát lại khi kết nối lại | Tự nhiên, được thiết kế cho chỉnh sửa khi ngắt kết nối |
| Độ phức tạp triển khai | Hàm biến đổi cho từng cặp thao tác, độ phức tạp O(n²) | Thiết kế cấu trúc dữ liệu và thu gom rác định kỳ |
| Độ trễ | Một vòng phản hồi đến máy chủ cho mỗi lô thao tác | Không có, áp dụng tức thì tại máy cục bộ rồi đồng bộ bất đồng bộ |
| Phù hợp nhất | Ứng dụng SaaS có backend, dữ liệu có cấu trúc và kiểu rõ ràng | Ứng dụng ưu tiên cục bộ, ngang hàng hoặc làm việc ngoại tuyến nhiều |
Có thể tham khảo thêm video “CRDTs: The Hard Parts” của Martin Kleppmann [2] và “How Collaborative Text Editors Don’t Break” của PawelCodeStuff [3] để có cái nhìn sâu hơn về các khía cạnh kỹ thuật và thực tiễn của hai phương pháp này.
Trong thực tế, các sản phẩm lớn đưa ra những lựa chọn khác nhau dựa trên yêu cầu cụ thể của từng trường hợp sử dụng.
Google Docs sử dụng OT với kiến trúc tập trung, phù hợp với văn bản thuần túy. Figma ban đầu dùng OT nhưng chuyển sang CRDT vào năm 2019 vì thiết kế đồ họa có cấu trúc phức tạp hơn. Notion và Linear áp dụng cách tiếp cận kết hợp: CRDT cho cấu trúc tài liệu và OT cho nội dung văn bản bên trong mỗi khối.
Dự án lựa chọn CRDT thông qua thư viện Yjs (Chương 2.7) dựa trên một số yêu cầu cụ thể của ứng dụng ghi chú cộng tác.
Trước tiên, khả năng làm việc ngoại tuyến là yêu cầu quan trọng với người dùng ghi chú. Khác với Google Docs vốn luôn yêu cầu kết nối ổn định, người dùng cần tiếp tục ghi chú ngay cả khi mất mạng và dữ liệu phải đồng bộ tự động khi kết nối được khôi phục, không cần thao tác thủ công.
Tiếp theo, BlockNote (Chương 2.11), thư viện soạn thảo được lựa chọn cho dự án, có tích hợp sẵn với Yjs, giúp giảm đáng kể công sức triển khai tính năng cộng tác mà không cần xây dựng lại từ đầu.
Cuối cùng, kiến trúc không phụ thuộc vào một máy chủ điều phối duy nhất mang lại khả năng mở rộng và khả năng chịu lỗi tốt hơn so với kiến trúc OT truyền thống, đặc biệt khi số lượng người dùng đồng thời trên một tài liệu tăng cao.
Yjs là thư viện CRDT (Conflict-free Replicated Data Type) viết bằng JavaScript, hỗ trợ real-time collaboration [4]. CRDT cho phép thay đổi từ nhiều người dùng tự động hợp nhất mà không cần xử lý xung đột thủ công.
Yjs được tổ chức xoay quanh các khái niệm chính sau:
Y.Doc: đơn vị trung tâm đại diện cho tài liệu collaborative, chứa shared data types và quản lý đồng bộ giữa các peer. Mỗi Y.Doc có clientID duy nhất để phân biệt thay đổi từ những người dùng khác nhau.
Shared Data Types: kiểu dữ liệu đặc biệt tự động đồng bộ giữa các peer, hoạt động tương tự kiểu JavaScript nhưng hợp nhất xung đột không cần máy chủ trung tâm và hỗ trợ observe thay đổi.
Provider: kết nối Y.Doc với peer khác qua các giao thức mạng, từ WebSocket đến WebRTC peer-to-peer hoặc dịch vụ đám mây.
Editor Binding: cầu nối đồng bộ shared types với editor phổ biến, biến editor thông thường thành collaborative editor.
| Kiến trúc | Mô tả | Ví dụ | Ghi chú |
|---|---|---|---|
| Y.Doc | Tài liệu gốc | new Y.Doc() |
Quản lý tất cả shared types |
| Shared Types | Dữ liệu tự động đồng bộ | Y.Array, Y.Map, Y.Text |
Có thể observe thay đổi |
| Provider | Kết nối mạng | WebsocketProvider, WebrtcProvider |
Chọn provider dựa trên nhu cầu |
| Editor Binding | Tích hợp editor | y-prosemirror, y-quill |
Biến editor thành collaborative |
Yjs cung cấp sáu shared data types, mỗi loại phục vụ mục đích khác nhau. Có thể lấy từ Y.Doc qua getter hoặc khởi tạo trực tiếp làm nested types.
| Kiểu dữ liệu | Mô tả | Ứng dụng |
|---|---|---|
Y.Array |
Mảng có thứ tự, hỗ trợ chèn/xóa theo chỉ số | Danh sách, hàng đợi |
Y.Map |
Key-value store, lồng ghép shared types khác | Cấu hình, metadata |
Y.Text |
Văn bản với hỗ trợ định dạng ký tự inline | Nội dung text có style |
Y.XmlFragment |
Fragment XML chứa nhiều node con | Cấu trúc block-based editor |
Mỗi shared type hỗ trợ observe để theo dõi thay đổi:
observe(): lắng nghe thay đổi trực tiếp trên type, trả về delta (Y.Array, Y.Text) hoặc key change (Y.Map)observeDeep(): lắng nghe thay đổi trên toàn bộ cây shared types, gồm nested typesYjs không đi kèm editor riêng mà tích hợp với editor phổ biến qua binding. Editor binding là cầu nối giữa Y.Text/Y.XmlFragment và editor, tự động đồng bộ nội dung và con trỏ.
| Editor | Gói binding | Mô tả |
|---|---|---|
| ProseMirror | y-prosemirror |
Toolkit rich text editor với document model có cấu trúc |
| Tiptap | @tiptap/extension-collaboration |
Framework headless rich text editor dựa trên ProseMirror |
Provider truyền tải thay đổi của Y.Doc giữa các peer, từ giao thức peer-to-peer đến dịch vụ đám mây có quản lý.
| Provider | Giao thức | Mô tả |
|---|---|---|
y-websocket |
WebSocket | Provider mặc định, client-server, hỗ trợ persistence và authentication |
y-webrtc |
WebRTC | Provider peer-to-peer, không cần server trung tâm |
| Hocuspocus | WebSocket | Server WebSocket chuyên dụng cho Yjs, hỗ trợ persistence, authentication, webhook và extension |
Dự án chọn Hocuspocus [5] làm provider chính nhờ khả năng mở rộng và tích hợp sâu với Tiptap/BlockNote.
Yjs cung cấp Y.UndoManager cho undo/redo trên shared types. Hỗ trợ scoped tracking (chỉ undo thay đổi từ nguồn cụ thể), capture timeout (gộp thay đổi trong 500ms), và metadata (ví dụ vị trí con trỏ).
Awareness chia sẻ thông tin trạng thái tạm thời giữa người dùng (con trỏ, vùng chọn, tên, màu sắc) thông qua CRDT riêng trong y-protocols. Dữ liệu tự động xóa khi người dùng ngắt kết nối, client không gửi tín hiệu trong 30 giây bị đánh dấu offline.
Yjs mang lại nhiều lợi ích:
Yjs có một số hạn chế:
ProseMirror là toolkit xây dựng rich text editor trên nền tảng web, phát triển bởi Marijn Haverbeke [6]. Không giống editor truyền thống cung cấp sẵn giao diện soạn thảo, ProseMirror cung cấp các viên gạch nền tảng để tự xây dựng editor phù hợp.
ProseMirror sử dụng document model có cấu trúc chặt chẽ thay vì xử lý HTML tự do, cho phép kiểm soát chính xác nội dung và hỗ trợ các tính năng nâng cao như collaborative editing, schema validation và transform recording.
| Thư viện | Chức năng | Vai trò |
|---|---|---|
prosemirror-model |
Schema, Document, Node, Mark | Định nghĩa cấu trúc tài liệu hợp lệ |
prosemirror-state |
EditorState, Transaction, Plugin | Quản lý trạng thái và luồng thay đổi |
prosemirror-view |
EditorView, DOM mapping, event handling | Rendering và tương tác người dùng |
prosemirror-transform |
Step, Transform, mapping | Ghi lại thay đổi dạng step có thể replay |
Trong dự án, ProseMirror đóng vai trò editor binding cho Yjs qua y-prosemirror:
y-prosemirror kết nối hai hệ thống, tạo collaborative editor hoàn chỉnhProseMirror là nền tảng cấp thấp xây dựng editor. Trong dự án, BlockNote sử dụng Tiptap làm lớp trung gian trên ProseMirror. Như vậy, ProseMirror là lõi hệ thống editor nhưng không dùng trực tiếp mà qua Tiptap.
Tiptap là headless rich text editor framework trên nền tảng ProseMirror [7], thuộc tổ chức ueberdosis (Tiptap Collective), cùng tổ chức phát triển Hocuspocus.
Khác biệt cốt lõi với ProseMirror là mức độ trừu tượng: ProseMirror là toolkit cấp thấp cung cấp công cụ nền tảng, Tiptap là framework hoàn chỉnh với API thân thiện, kiến trúc extension linh hoạt và tích hợp sẵn nhiều UI framework.
Tiptap được thiết kế theo ba trụ cột chính:
true nếu thành công.Bảng dưới đây phân biệt rõ vai trò của ProseMirror và Tiptap trong hệ sinh thái:
| Tiêu chí | ProseMirror | Tiptap |
|---|---|---|
| Mức độ | Low-level toolkit | High-level framework |
| Mục đích | Cung cấp công cụ nền tảng | Cho phép tạo editor nhanh, dễ dàng |
| Kiến trúc | 4 thư viện riêng, tích hợp thủ công | Thống nhất, extension-based |
| Sử dụng | Cần tự xây dựng editor từ đầu | Cấu hình qua extensions, dùng ngay |
| UI Framework | Không hỗ trợ sẵn | Hỗ trợ React, Vue, Svelte |
| Hệ sinh thái | Cộng đồng, plugin rải rác | Kho extension tập trung, Pro features |
Tiptap cung cấp extension @tiptap/extension-collaboration tích hợp Yjs (Chương 2.7):
y-prosemirrorKết hợp với Hocuspocus (Chương 2.10), Tiptap trở thành collaborative editor hoàn chỉnh với máy chủ đồng bộ qua WebSocket.
BlockNote (Chương 2.11) xây dựng trên Tiptap và ProseMirror: ProseMirror là động cơ (document model, transform, state management), Tiptap là khung gầm (extension system, commands, events), BlockNote là thân xe hoàn chỉnh (block component cho soạn thảo block-based).
Hocuspocus là server WebSocket chuyên dụng cho Yjs, phát triển bởi Tiptap Collective (ueberdosis) [5] – cùng tổ chức đứng sau Tiptap, thiết kế làm backend collaboration cho editor dựa trên ProseMirror và Tiptap.
Hocuspocus đóng vai trò trung gian giữa các client, nhận và phân phối cập nhật Yjs document qua WebSocket, đồng thời quản lý persistence, authentication, awareness và cung cấp hệ thống extension.
Hocuspocus được thiết kế theo kiến trúc server-client đơn giản:
Hocuspocus Server: tiến trình server, quản lý kết nối WebSocket, đồng bộ Yjs documents và phân phối cập nhật đến client trong cùng phòng (room).
Hocuspocus Provider: thư viện client (@hocuspocus/provider), kết nối server qua WebSocket, đồng bộ Y.Doc, quản lý vòng đời kết nối, xác thực và awareness.
Mỗi tài liệu xác định bởi tên phòng (room name). Client kết nối cùng phòng tự động đồng bộ tài liệu Yjs tương ứng.
Luồng hoạt động:
onAuthenticate, nếu thành công mở phiên làm việcHocuspocus có hệ thống extension mở rộng chức năng server qua hook vòng đời:
Persistence: lưu trữ tài liệu. Extension có sẵn:
@hocuspocus/extension-sqlite: SQLite@hocuspocus/extension-postgresql: PostgreSQL@hocuspocus/extension-s3: S3-compatible storage@hocuspocus/extension-redis: đồng bộ nhiều instance serverAuthentication: xác thực qua hook onAuthenticate, kiểm tra token, API key
Lifecycle Hooks: onConnect, onLoadDocument, onChange, onStoreDocument, onDisconnect
Tính năng chính:
Trong dự án, Hocuspocus là provider chính cho Yjs (Chương 2.7) vì:
BlockNote là một thư viện editor được xây dựng trên nền tảng Tiptap và ProseMirror [8]. BlockNote cung cấp một công cụ soạn thảo văn bản phong phú với kiến trúc block-based tương tự như Notion, cho phép người dùng xây dựng các khối nội dung một cách linh hoạt.
BlockNote được thiết kế để dễ tích hợp vào các ứng dụng React (Chương 2.3.2), với API rõ ràng và khả năng tùy chỉnh cao, tương thích với màu sắc của shadcnui . Thư viện này hoạt động dựa trên ProseMirror, một editor framework mạnh mẽ và có cấu trúc rõ ràng. Có thể hình dung ProseMirror như một bộ công cụ xây dựng editor, trong khi BlockNote là một implementation cụ thể, dễ dàng sử dụng nhanh.
BlockNote là một hệ sinh thái mã nguồn mở hoàn toàn, miễn phí sử dụng công cộng. Chỉ riêng các gói thư viện @blocknote/xl-* có giấy phép copyleft, yêu cầu mua giấy phép nếu sử dụng trong sản phẩm mã nguồn đóng, hoặc thương mại.
Model dữ liệu của BlockNote được tổ chức thành các block, mỗi block đại diện cho một phần nội dung riêng biệt, như đoạn văn, hình ảnh, bảng,… Mỗi block có một cấu trúc dữ liệu riêng, bao gồm loại block, nội dung và các thuộc tính liên quan (xem thêm về cấu trúc dữ liệu của block trong BlockNote tại Phụ lục A.1).
BlockNote mang lại nhiều lợi ích cho phát triển editor:
Bên cạnh các ưu điểm, BlockNote có một số hạn chế:
OpenAPI là một specification cho mô tả HTTP APIs theo cách chuẩn hóa. OpenAPI cho phép sinh code từ contracts, tạo điều kiện cho contract-first development, và tự động tạo documentation.
OpenAPI (trước đây được gọi là Swagger) là một format standardized cho mô tả RESTful APIs. OpenAPI specification định nghĩa endpoints, parameters, request/response schemas, và error codes theo cách machine-readable.
heyapi/openapi-ts [9] sinh TypeScript types từ OpenAPI specoapi-codegen [10] sinh Go code từ OpenAPI spec, hỗ trợ HTTP API generationtypescript-nestjs-server [12]Contract-first approach định nghĩa API contracts trước khi implement logic, đảm bảo tính consistency, tạo điều kiện cho parallel development, và giảm integration issues.
Casbin là một framework authorization mã nguồn mở mạnh mẽ và linh hoạt, hiện thuộc Apache Software Foundation. Casbin hỗ trợ các mô hình kiểm soát truy cập như ACL, RBAC, ABAC, và các biến thể khác. Framework này cho phép định nghĩa các rule authorization một cách khai báo thông qua cấu hình, thay vì hardcode logic kiểm soát.
Casbin được thiết kế để hoạt động với nhiều ngôn ngữ lập trình, bao gồm Go, Java, Python, Node.js và hơn thế nữa, giúp đảm bảo tính nhất quán trong authorization logic trên toàn bộ hệ sinh thái.
So với các giải pháp authorization khác như OPA, SpiceDB (Google Zanzibar opensource), Casbin tập trung vào sự đơn giản và hiệu quả, cung cấp một cách tiếp cận.
Casbin mang lại nhiều lợi ích cho phát triển authorization:
Bên cạnh các ưu điểm, Casbin có một số hạn chế:
Chương 3 trình bày quá trình phân tích và thiết kế hệ thống nhằm xác định rõ cấu trúc và cách thức hoạt động của hệ thống. Nội dung chương bao gồm khảo sát hiện trạng, xác định yêu cầu chức năng và phi chức năng, từ đó xây dựng kiến trúc hệ thống và mô tả các thành phần chính. Bên cạnh đó, chương sử dụng các sơ đồ UML để mô hình hóa hành vi, luồng xử lý và mối quan hệ giữa các đối tượng trong hệ thống, thiết kế dữ liệu thông qua các bảng cơ sở dữ liệu. Hệ thống cũng phân tích BlockNote schema tuỳ chỉnh, và mô hình thư viện Casbin của hệ thống.
Hiện nay có nhiều hệ thống quản lý kiến thức cá nhân khác nhau, mỗi hệ thống đều có những ưu điểm và nhược điểm riêng. Dưới đây là một số hệ thống phổ biến mà nhóm đã khảo sát.
Notion
Notion là một nền tảng làm việc tích hợp được ra mắt vào năm 2013, kết hợp tài liệu, cơ sở dữ liệu, wiki và quản lý dự án trong một nền tảng duy nhất. Với hơn 30 triệu người dùng toàn cầu, Notion đã phát triển từ một ứng dụng ghi chú đơn giản thành một hệ thống kiến thức toàn diện.
Lịch sử phát triển của Notion bắt đầu với phiên bản 1.0 tập trung vào hợp tác tài liệu thời gian thực và tổ chức tài liệu phong cách wiki. Điểm đột phá lớn nhất đến với phiên bản 2.0 vào tháng 3 năm 2018 khi giới thiệu tính năng Database, nâng cấp Notion lên một tầm cao mới và cho phép tạo ra các công cụ ứng dụng. Hiện tại, Notion liên tục được cập nhật với các tính năng mới như AI agents, hệ thống tự động hóa hoàn chỉnh và nhiều loại giao diện cơ sở dữ liệu mới.
Các tính năng chính bao gồm editor module hóa với các khối có thể sắp xếp linh hoạt, cơ sở dữ liệu đa dạng (bảng, Kanban, lịch trình, gallery), khả năng hợp tác thời gian thực, và tích hợp AI. Giao diện của Notion được đánh giá cao về tính thẩm mỹ và sự linh hoạt, cho phép người dùng tùy chỉnh không gian làm việc theo nhu cầu riêng.
Tuy nhiên, Notion cũng có những hạn chế nhất định, điển hình như không hỗ trợ graph view một cách trực quan.
Obsidian
Obsidian là một ứng dụng ghi chú và quản lý kiến thức dựa trên tệp Markdown, được phát triển bởi Shida Li và Erica Xu và ra mắt vào năm 2020. Với triết lý “local-first”, Obsidian đảm bảo dữ liệu của người dùng luôn nằm trên thiết bị của họ, mang lại quyền sở hữu và tính linh hoạt tối đa.
Obsidian hoạt động với một “vault” chứa các tài liệu văn bản, mỗi ghi chú mới tạo ra một tệp Markdown riêng. Điểm mạnh nổi bật nhất của Obsidian là khả năng liên kết hai chiều và chế độ xem graph giúp người dùng hình dung mối quan hệ giữa các ghi chú. Người dùng có thể tạo liên kết nội dung bằng Wikilinks hoặc liên kết Markdown thông thường, và tất cả các liên kết này đều được hiển thị trong graph view tương tác.
Các tính năng chính bao gồm chế độ xem graph giúp phân tích mối quan hệ giữa các ghi chú, Canvas cho phép sắp xếp ghi chú trực quan không giới hạn, hệ thống plugin mở rộng do cộng đồng phát triển, và khả năng tích hợp với nhiều công cụ khác. Obsidian cũng hỗ trợ chế độ chỉnh sửa song song giữa Source Mode và Live Preview, cùng với đầy đủ các công cụ tìm kiếm và tổ chức. Obsidian còn chính hỗ trợ vim motion cho những người dùng yêu thích trải nghiệm chỉnh sửa văn bản nâng cao.
Mặc dù mạnh mẽ, Obsidian cũng có những thách thức. Learning curve khá cao, đặc biệt với người mới bắt đầu. Thiếu tính năng hợp tác thời gian thực là một hạn chế quan trọng. Giao diện mặc định có thể trông đơn giản so với các đối thủ cạnh tranh. Obsidian phù hợp nhất với các nhà nghiên cứu, nhà văn, lập trình viên và người làm việc kiến thức muốn kiểm soát sâu sắc ghi chú của mình.
jackyzha0/quartz
Quartz là một static-site generator nhanh, được thiết kế để biến nội dung Markdown thành các website hoàn toàn chức năng. Được phát triển bởi jackyzha0, Quartz hoạt động như một giải pháp self-hosted thay thế cho Obsidian Publish, cho phép người dùng xuất bản ghi chú và digital gardens của mình lên web một cách dễ dàng.
Quartz tương thích hoàn toàn với Obsidian, hỗ trợ đầy đủ các tính năng như wikilinks, backlinks, transclusions, graph view và full-text search. Nó được xây dựng trên công nghệ TypeScript và cung cấp khả năng tùy chỉnh cao thông qua JSX layouts và page components. Một trong những điểm mạnh của Quartz là tốc độ tải trang cực nhanh và kích thước bundle nhỏ.
Các tính năng chính bao gồm hỗ trợ LaTeX, Mermaid diagrams, dark mode, breadcrumbs, popover previews, internationalization, và hệ thống comments. Quartz cũng hỗ trợ Docker, RSS feeds, và có hệ thống plugin mở rộng. Nó đặc biệt phù hợp cho các cá nhân muốn chia sẻ kiến thức của mình dưới dạng website công khai hoặc digital garden.
Mặc dù mạnh mẽ, Quartz cũng có những hạn chế. Nó yêu cầu kiến thức kỹ thuật về Git và hosting để thiết lập. Không có tính năng hợp tác thời gian thực như Notion. Giao diện quản lý nội dung không trực quan bằng các ứng dụng desktop. Quartz phù hợp nhất với các nhà phát triển, sinh viên, và giáo viên muốn xuất bản ghi chú của mình dưới dạng website.
| Đặc tính |
|
|
|
|---|---|---|---|
| Loại | Nền tảng làm việc | Ứng dụng ghi chú desktop | Static site generator |
| Nền tảng hỗ trợ | Web, Desktop, Mobile | Desktop, Mobile | CLI |
| Hợp tác thời gian thực | Có | Không | Không |
| Graph View | Không | Có | Có |
| Liên kết hai chiều | Có | Có | Có |
| Markdown gốc | Không (block-based) | Có | Có |
| Hệ thống Plugin | Có (kho mẫu, AI) | Có (cộng đồng) | Có |
| Lưu trữ | Đám mây | Local-first | Git-based |
| Self-hosted | Không | Không | Có |
| Mã nguồn mở | Không | Không | Có (MIT) |
| Xuất bản web | Có (Notion Sites) | Có (Obsidian Publish) | Có (tính năng chính) |
Xuất phát từ đó, nhóm đã xác định được những điểm mạnh và hạn chế của từng hệ thống, từ đó rút ra những bài học quan trọng để áp dụng vào thiết kế hệ thống của mình. Nhưng cũng cần nhấn mạnh rằng Notopia không nhằm mục đích thay thế hoàn toàn các hệ thống hiện có, mà chỉ thực hiện một số chức năng nhất định, mang tính chất nghiên cứu, học tập, áp dụng công nghệ và quy trình phát triển phần mềm.
Dự án được chia thành nhiều service khác nhau, bao gồm note service, document service, authorization service, search-worker worker, và các thành phần tái sử dụng từ các dịch vụ sẵn có. Mỗi service có trách nhiệm riêng biệt và giao tiếp với nhau thông qua API và message queue để đảm bảo tính modular và dễ bảo trì.
Dưới đây là sơ đồ kiến trúc tổng quan của hệ thống.
Trong đó, các thành phần chính bao gồm:
| Thành phần | Mô tả |
| User | Người dùng tương tác với hệ thống thông qua giao diện web |
| Gateway | Điểm vào chính của hệ thống, chịu trách nhiệm định tuyến yêu cầu đến các service phù hợp |
| Web App | Ứng dụng web cung cấp giao diện người dùng để tạo và quản lý ghi chú |
| Note Service | Quản lý metadata và logic liên quan đến không gian làm việc và ghi chú |
| Document Service | Quản lý nội dung của các ghi chú |
| Authorization Service | Xác định quyền truy cập của người dùng đối với các tài nguyên |
| Identity Provider | Dịch vụ xác thực và quản lý người dùng |
| Object Storage | Lưu trữ các tệp liên quan đến ghi chú, như hình ảnh và tài liệu |
| Search Service | Cung cấp khả năng tìm kiếm nội dung trong ghi chú |
| Event Bus | Hệ thống message queue để giao tiếp giữa các service |
| Monitoring | Giám sát hiệu suất và trạng thái của hệ thống |
Chi tiết về các thành phần chính:
Gateway
Gateway là điểm vào chính của hệ thống, chịu trách nhiệm định tuyến các yêu cầu từ người dùng đến các service phù hợp. Nó cũng thực hiện các chức năng như xác thực, và cân bằng tải để đảm bảo hiệu suất và bảo mật của hệ thống.
Hệ thống sử dụng Traefik làm API Gateway, với đặc điểm gọn nhẹ và dễ cấu hình, tương thích tốt với docker compose. Sử dụng agilezebra/jwt-middleware [13] để xử lý xác thực JWT, chuyển hoá OIDC claim thành header HTTP, giúp các service phía sau có thể dễ dàng xác định người dùng, không cần phải tích hợp trực tiếp với Identity Provider.
Web App
Web App là giao diện người dùng chính, cho phép người dùng tạo, chỉnh sửa và quản lý ghi chú. Ứng dụng được xây dựng với React và NextJS (Chương 2.3).
Note Service
Note Service (note service), viết bằng Go, chịu trách nhiệm quản lý metadata và logic liên quan đến không gian làm việc và ghi chú. Dịch vụ này cung cấp API để Web App có thể tương tác với không gian làm việc và ghi chú, đồng thời tích hợp với Authorization Service để kiểm tra quyền truy cập của người dùng trước khi thực hiện các hành động liên quan đến thư mục, ghi chú.
Document Service
Document Service (document service), viết bằng Typescript và NestJS framework, chịu trách nhiệm quản lý nội dung của các ghi chú, bao gồm lưu trữ và truy xuất dữ liệu. Nó cung cấp API để Web App có thể tương tác với nội dung ghi chú, tích hợp với Object Storage để lưu trữ các tệp liên quan, và sử dụng Hocuspocus (Chương 2.7) để hỗ trợ cộng tác thời gian thực trên nội dung ghi chú.
Authorization Service
Authorization Service (authorization service), viết bằng Go, sử dụng thư viên Casbin (Chương 2.13) để quản lý quyền truy cập của người dùng đối với không gian làm việc và ghi chú. Dịch vụ này cung cấp API để các service khác có thể kiểm tra quyền truy cập của người dùng trước khi thực hiện các hành động liên quan đến thư mục, ghi chú.
Identity Provider
Identity Provider chịu trách nhiệm xác thực người dùng và quản lý thông tin tài khoản. Hệ thống sử dụng Authentik là giải pháp xác thực, cung cấp các tính năng như đăng nhập một lần (SSO), quản lý người dùng, và hỗ trợ nhiều phương thức xác thực. Hệ thống sử dụng OpenID Connect (OIDC) để tích hợp giữa Gateway, Web App với Identity Provider, giúp đơn giản hóa quá trình xác thực và quản lý người dùng.
Object Storage
Object Storage được sử dụng để lưu trữ các tệp liên quan đến ghi chú, như hình ảnh và tài liệu. Hệ thống sử dụng RustFS, một giải pháp lưu trữ đối tượng nhẹ và hiệu quả, cung cấp API tương thích với S3 để dễ dàng tích hợp với các service khác.
Search Service
Search Service (Meilisearch), viết bằng Rust, cung cấp khả năng tìm kiếm nội dung trong ghi chú. Trong đó, search-worker worker chịu trách nhiệm đồng bộ dữ liệu đến Search Service để đảm bảo dữ liệu tìm kiếm luôn được cập nhật.
Event Bus
Event Bus là hệ thống message queue được sử dụng để giao tiếp giữa các service, đảm bảo tính modular và giảm sự phụ thuộc trực tiếp giữa các service. Hệ thống sử dụng Redpanda, một giải pháp message queue hiệu suất cao, tương thích với Kafka API, giúp dễ dàng tích hợp với các service khác.
Monitoring
Monitoring là thành phần quan trọng để giám sát hiệu suất và trạng thái của hệ thống. Hệ thống sử dụng Grafana Stack và Prometheus để thu thập và hiển thị các chỉ số về hiệu suất.
note serviceLà thành phần trung tâm trong hệ thống, note service chịu trách nhiệm quản lý metadata và logic liên quan đến ghi chú. Dịch vụ này được thiết kế theo kiến trúc Clean Architecture, Domain Driven Design, và Event-Driven Architecture để đảm bảo tính modular, dễ bảo trì, và khả năng mở rộng trong tương lai4. Dưới đây là sơ đồ kiến trúc chi tiết của note service.
note service
document servicedocument service áp dụng kiến trúc theo NestJS, với các module được tổ chức theo chức năng. Dưới đây là sơ đồ kiến trúc chi tiết của document service, mô tả đến cấp độ module.
document service
authorization serviceauthorization service được thiết kế theo kiến trúc Layered Architecture, với tầng logic không phân tách rõ vì tính đặc thù của thư viện Casbin. Dưới đây là sơ đồ kiến trúc của authorization service.
authorization service
search-worker workersearch-worker worker được thiết kế theo kiến trúc đơn giản, sử dụng trên 1 module chính của NestJS, không chia thành nhiều module nhỏ. Dưới đây là sơ đồ kiến trúc của search-worker worker.
search-worker worker
Nhóm chỉ xác định một vài use case phức tạp, có nhiều luồng, tác động đến nhiều dịch vụ.
| Trường | Nội dung |
|---|---|
| ID | UC01 |
| Name | Create Note |
| Description | Use case này mô tả quy trình tạo ghi chú mới |
| Actor(s) | User |
| Priority | Cao |
| Trigger | Người dùng muốn tạo một ghi chú mới |
| Pre-condition(s) |
|
| Post-condition(s) |
|
| Basic Flow |
|
| Alternate Flow |
|
| Exception Flow |
|
| Trường | Nội dung |
|---|---|
| ID | UC02 |
| Name | Get Note |
| Description | Use case này mô tả quy trình lấy nội dung ghi chú và chuyển sang chế độ chỉnh sửa |
| Actor(s) | User |
| Priority | Cao |
| Trigger | Người dùng chọn một ghi chú để xem hoặc chỉnh sửa |
| Pre-condition(s) |
|
| Post-condition(s) |
|
| Basic Flow |
|
| Alternate Flow |
|
| Exception Flow |
|
| Trường | Nội dung |
|---|---|
| ID | UC03 |
| Name | Commit Document |
| Description | Use case này mô tả quy trình lưu và publish event cập nhật tài liệu (nội dung của ghi chú) |
| Actor(s) | User |
| Priority | Cao |
| Trigger | Người dùng chọn lưu tài liệu để cập nhật |
| Pre-condition(s) |
|
| Post-condition(s) |
|
| Basic Flow |
|
| Alternate Flow |
|
| Exception Flow |
|
Dưới đây là sơ đồ lớp cho các service trong hệ thống, bao gồm note service và document service, không bao gồm authorization service vì tính đặc thù của service không thể biểu diễn một cách dễ dàng, và search-worker vì xử lý dữ liệu không phức tạp. Sơ đồ lớp giúp minh họa cấu trúc của các lớp, các thuộc tính và phương thức của chúng, cũng như mối quan hệ giữa các lớp.
Sơ đồ lớp cho note service được chia thành hai phần: tầng application và tầng domain. Tầng application tập trung vào các lớp mô hình dữ liệu và interface của service, trong khi tầng domain tập trung vào các lớp đại diện cho các aggregate và logic nghiệp vụ.
Sơ đồ lớp tầng application
note service
Tầng application bao gồm các lớp mô hình dữ liệu (model) cho việc read (theo CQRS) và các interface của service. Các lớp mô hình dữ liệu đại diện cho các thực thể trong hệ thống như Note, Folder, Workspace, v.v. Các interface của service định nghĩa các phương thức mà tầng infrastructure sẽ triển khai.
Sơ đồ lớp tầng domain
note service
Tầng domain, áp dụng Domain Driven Design Pattern, bao gồm các lớp đại diện cho các aggregate như Note, Folder, Workspace, v.v. Các lớp này chứa các thuộc tính và phương thức liên quan đến logic nghiệp vụ của chúng. Ngoài ra, tầng domain cũng bao gồm các interface của repository để truy cập dữ liệu. Tuy nhiên, tầng domain không được trực tiếp gọi service được định nghĩa hay repository interface được khai báo.
document service
Sơ đồ tinh gọn cho document service, bao gồm các thực thể, services, và đối tượng liên quan. Ở thiết kế này không bao gồm repository vì đã được TypeORM abstract đi, tức, service sẽ trực tiếp gọi các phương thức của TypeORM để truy cập dữ liệu.
Các service trong hệ thống sẽ sử dụng cơ sở dữ liệu PostgreSQL để lưu trữ và quản lý dữ liệu. Dưới đây là thiết kế cơ sở dữ liệu cho các service, bao gồm các bảng chính và mối quan hệ giữa chúng.
note servicenote service
Bảng workspaces
note service
| Tên cột | Kiểu dữ liệu | Mô tả | Khóa |
|---|---|---|---|
id |
UUID |
Mã định danh duy nhất của workspace | PK |
slug |
TEXT |
URL-friendly slug của workspace | UQ |
name |
TEXT |
Tên của workspace | |
created_at |
TIMESTAMPTZ |
Thời gian tạo | |
updated_at |
TIMESTAMPTZ |
Thời gian cập nhật gần nhất | |
deleted_at |
TIMESTAMPTZ |
Thời gian xóa (soft delete, Nullable) |
Bảng folders
note service
| Tên cột | Kiểu dữ liệu | Mô tả | Khóa |
|---|---|---|---|
id |
UUID |
Mã định danh duy nhất của folder | PK |
name |
TEXT |
Tên của folder (Nullable) | |
icon |
TEXT |
Icon của folder (Nullable) | |
workspace_id |
UUID |
ID workspace chứa folder này | FK |
parent_id |
UUID |
ID folder cha (nested structure) (Nullable) | FK |
created_at |
TIMESTAMPTZ |
Thời gian tạo | |
updated_at |
TIMESTAMPTZ |
Thời gian cập nhật gần nhất | |
trashed_by |
ENUM |
Loại xóa (purpose|parent, Nullable) | |
trashed_at |
TIMESTAMPTZ |
Thời gian xóa (Nullable) |
Bảng notes
note service
| Tên cột | Kiểu dữ liệu | Mô tả | Khóa |
|---|---|---|---|
id |
UUID |
Mã định danh duy nhất của ghi chú | PK |
name |
TEXT |
Tên của ghi chú | |
icon |
TEXT |
Icon của ghi chú (Nullable) | |
folder_id |
UUID |
ID folder chứa ghi chú này | FK |
tags |
TEXT[] |
Danh sách tag của ghi chú (Nullable) | |
size |
INTEGER |
Kích thước của ghi chú (bytes) | |
created_at |
TIMESTAMPTZ |
Thời gian tạo | |
updated_at |
TIMESTAMPTZ |
Thời gian cập nhật gần nhất | |
trashed_by |
ENUM |
Loại xóa (purpose|parent, Nullable) | |
trashed_at |
TIMESTAMPTZ |
Thời gian xóa (Nullable) |
Bảng note_links
note service
| Tên cột | Kiểu dữ liệu | Mô tả | Khóa |
|---|---|---|---|
source_id |
UUID |
ID ghi chú nguồn | PK, FK |
target_id |
UUID |
ID ghi chú đích | PK, FK |
document servicedocument service
Bảng documents
document service
| Tên cột | Kiểu dữ liệu | Mô tả | Khóa |
|---|---|---|---|
id |
UUID |
Mã định danh duy nhất của document | PK |
data |
BYTEA |
Dữ liệu nhị phân yjs5 | |
modified |
BOOLEAN |
Trạng thái đã được chỉnh sửa hay chưa |
Bảng Revisions
document service
| Tên cột | Kiểu dữ liệu | Mô tả | Khóa |
|---|---|---|---|
id |
UUID |
Mã định danh duy nhất của revision | PK |
name |
TEXT |
Tên của revision (Nullable) | |
data |
JSON |
Dữ liệu BlockNote | |
document_id |
UUID |
ID document liên kết với revision | FK |
created_at |
TIMESTAMPTZ |
Thời gian tạo | |
deleted_at |
TIMESTAMPTZ |
Thời gian xóa (Nullable) |
authorization serviceVì tính đặc thù của thư viện Casbin, bảng dữ liệu của service này không thuộc phạm vi quản lý của hệ thống, mà sẽ được Casbin tự động tạo ra và quản lý.
authorization service
Bảng casbin_rules
authorization service
| Tên cột | Kiểu dữ liệu | Mô tả | Khóa |
|---|---|---|---|
id |
UUID |
Mã định danh duy nhất của rule | PK |
ptype |
TEXT |
Loại rule (p, g, g2, v.v…) | |
v0 |
TEXT |
Trường dữ liệu 0 | |
v1 |
TEXT |
Trường dữ liệu 1 | |
v2 |
TEXT |
Trường dữ liệu 2 | |
v3 |
TEXT |
Trường dữ liệu 3 (Nullable) | |
v4 |
TEXT |
Trường dữ liệu 4 (Nullable) | |
v5 |
TEXT |
Trường dữ liệu 5 (Nullable) |
BlockNote bao gồm nhiều schema nhiều schema, có thể xem tại Chương 2.11. Tuy nhiên, để có thể liên kết các ghi chú với nhau, cũng như hỗ trợ liên kết thông qua tag, hệ thống cần tuỳ chỉnh thêm hai config schema cho BlockNote, là reference và tag. Tham khảo từ tài liệu schema tuỳ chỉnh của BlockNote [16], hai config này sẽ được định nghĩa như sau.
Một khối reference sẽ chứa một thuộc tính noteId để xác định ghi chú mà nó đang tham chiếu đến. Khi người dùng chèn một khối reference vào ghi chú, họ sẽ có thể chọn một ghi chú khác trong hệ thống để liên kết đến. Điều này cho phép tạo ra các mối quan hệ giữa các ghi chú.
Khối tag sẽ chứa một thuộc tính tag để lưu trữ tên của tag. Người dùng có thể chèn một khối tag vào ghi chú và gán cho nó một tên tag cụ thể.
Casbin hỗ trợ biểu diễn nhiều mô hình kiểm soát truy cập khác nhau, chi tiết xem tại Chương 2.13. Trong đó, hệ thống tập trung sử dụng mô hình RBAC để quản lý quyền truy cập dựa trên vai trò (role) của người dùng, trong mỗi không gian làm việc. Đối với Casbin, workspace được xem như một domain.
Trong đó, các phần được định nghĩa như sau:
request_definition định nghĩa cấu trúc của một yêu cầu truy cập, bao gồm người dùng (sub), không gian làm việc (dom), đối tượng (obj) và hành động (act)policy_definition định nghĩa cấu trúc của một chính sách, bao gồm vai trò (sub), đối tượng (obj) và hành động (act)role_definition định nghĩa cách thức xác định vai trò của người dùng trong một không gian làm việc (g) và cách thức xác định mối quan hệ giữa đối tượng và chính sách (g2)policy_effect định nghĩa hiệu ứng của chính sách, trong trường hợp này là cho phép truy cập nếu có ít nhất một chính sách cho phépmatchers định nghĩa cách thức so khớp giữa yêu cầu truy cập và chính sách, trong đó yêu cầu truy cập sẽ được phép nếu người dùng có vai trò phù hợp trong không gian làm việc, đối tượng phù hợp với chính sách và hành động phù hợp với chính sáchTrong đó, các chính sách được định nghĩa như sau:
owner có quyền đọc, chỉnh sửa và xóa trên không gian làm việc và các mục trong không gian làm việceditor có quyền đọc trên không gian làm việc và quyền đọc, chỉnh sửa và xóa trên các mục trong không gian làm việcviewer chỉ có quyền đọc trên không gian làm việc và các mục trong không gian làm việcnote và folder được xác định là các mục trong không gian làm việc thông qua mối quan hệ g2Ví dụ minh hoạ cụ thể về yêu cầu truy cập và quá trình so khớp chính sách có thể xem tại Phụ lục B.1.
Health check endpoint là cơ chế tự kiểm tra giúp hệ thống giám sát phát hiện sớm sự cố (lỗi kết nối DB, rò rỉ bộ nhớ, thành phần phụ thuộc sập). Từ đó, Kubernetes có thể tự động khởi động lại container hoặc ngừng gửi traffic đến service lỗi.
Có 3 loại probe theo Kubernetes:
Bảng dưới đây tóm tắt trạng thái triển khai health check. Do thời gian có hạn, nhóm chỉ triển khai đầy đủ cho các Go service; các service còn lại sẽ bổ sung sau.
| Service | Startup | Liveness | Readiness |
|---|---|---|---|
note (Go) |
✓ | ✓ | ✓ |
authorization (Go) |
✓ | ✓ | ✓ |
document (NestJS) |
– | – | – |
search-worker (NestJS) |
– | – | – |
web (NextJS) |
– | – | – |
Ví dụ chi tiết payload response của từng loại probe trên note service có thể xem tại Phụ lục C.1.
Sử dụng thư viện github.com/alexliesenfeld/health [17]. Thư viện hỗ trợ kiểm tra đồng bộ và bất đồng bộ (chạy tác vụ nặng trong nền theo chu kỳ, trả về kết quả từ cache), cấu hình TTL cho cache để giảm tải hạ tầng, và bọc tích hợp với các thư viện health check phổ biến khác (health-go, etherlabsio/healthcheck, heptiolabs/healthcheck, InVisionApp/go-health).
Nx hướng developer dùng Nx Cloud cho CI, nhưng nhóm đã tự xây dựng giải pháp cache riêng cho GitHub Actions: KevinNitroG/nx-cache-action [18]. Script lấy cảm hứng từ raegen/nx [19] (đã deprecated), nhưng cơ chế khác biệt: khởi động NodeJS ExpressJS server implement OpenAPI Spec của Nx [20], forward lệnh Nx cho child process, server nhận request cache và giao tiếp với GitHub Actions cache API qua actions/toolkit/cache [21].
Vấn đề: Nx lưu cache task khoảng một tháng kể từ lần cuối sử dụng. Không dùng nx-cache-action, cache được lưu toàn bộ project task cache ( 5GB/lần). 10 commit thay đổi source code tạo 10 cache ( 50GB), vượt giới hạn 10GB của GitHub Actions. Chưa kể còn phải cache node modules, Go packages, system dependencies.
Với nx-cache-action, cache lưu theo từng project nhỏ (vài trăm KB đến hơn 10MB/task), chỉ tạo cache mới khi project đó thay đổi. Điều này tối ưu dung lượng, tránh vượt giới hạn GitHub Actions, tăng cache hit.
Nhược điểm: cache phải download qua actions/toolkit/cache rồi pipe vào lại Nx process qua HTTP, thay vì giao tiếp trực tiếp với GitHub Actions cache Rest API. Đây là cách dễ dàng nhất để triển khai.
Chương trình bày kết quả xây dựng Web App, bao gồm giao diện, các thành phần giao diện, và mô tả chức năng của từng thành phần.
Chương này trình bày phần kết luận của đồ án, nhằm tổng hợp và đánh giá các kết quả đạt được trong quá trình nghiên cứu, phân tích, thiết kế và xây dựng hệ thống. Nội dung chương tập trung vào việc đánh giá sản phẩm đã triển khai, nhận xét những thuận lợi, khó khăn, ưu điểm và hạn chế của hệ thống, đồng thời đề xuất các hướng phát triển trong tương lai nhằm nâng cao tính hoàn thiện và khả năng ứng dụng thực tế.
Nhóm đã hoàn thiện hệ thống ghi chú với các tính năng:
Dự án áp dụng các công nghệ hiện đại:
vtuanjs/sqlc-gen-go [22] cho dynamic WHERE conditions.TrshPuppy/obsidian-notes [23]) không chuẩn xác do parse bằng text, code block chứa #/#! bị parse thành tag. Hệ thống không support nested tags như Obsidian.rustfs/rustfs/issues/2587 [24] do thành viên nhóm phát hiện.note.note, authorization) có health check endpoint đầy đủ.note).@blocknote/xl-ai [25], hybrid search với Meilisearch [26], tool thông qua API hệ thống.Dự án đã đạt được mục tiêu đề ra và mang lại nhiều bài học quý giá cho nhóm phát triển. Hệ thống “Notopia - Ứng dụng ghi chú thông minh hỗ trợ quản lý tri thức bằng biểu đồ quan hệ” không chỉ là sản phẩm hoàn chỉnh mà còn là nền tảng để tiếp tục nghiên cứu và phát triển trong tương lai.
Phụ lục A. BLOCKNOTE
Dưới đây là ví dụ về cấu trúc dữ liệu của một block trong BlockNote:
Phụ lục B. CASBIN
Giả sử ta có ba người dùng: 110, 111 và 112, với các vai trò và quyền truy cập như sau:
Trong đó:
111 là chủ sở hữu (owner) của không gian làm việc 111112 là biên tập viên (editor) của không gian làm việc 111 và chủ sở hữu của không gian làm việc 112110 là người xem (viewer) của không gian làm việc 111 và chủ sở hữu của không gian làm việc 110| Loại | Code |
|---|---|
| Request | |
| Result |
Người dùng 111 yêu cầu đọc không gian làm việc 111. Kết quả là true vì người dùng 111 có vai trò owner trong không gian làm việc 111 và có chính sách cho phép đọc không gian làm việc. Các bước suy luận dựa trên matcher m:
111 có vai trò owner trong không gian làm việc 111 thông qua chính sách g. Ta có thể xác định rằng g(user:111, owner, workspace:111) là true.workspace cũng chứa trong workspace (
)
thông qua chính sách g2. Ta có thể xác định rằngg2(workspace, workspace) là true.read phù hợp với chính sách read của vai trò owner trên không gian làm việc. Ta có thể xác định rằng read == read là true.Người dùng 112 yêu cầu xóa không gian làm việc 111. Kết quả là false vì người dùng 112 chỉ có vai trò editor trong không gian làm việc 111 và không có chính sách nào cho phép editor xóa không gian làm việc. Các bước suy luận:
112 có vai trò editor trong không gian làm việc 111 thông qua chính sách g. Ta có thể xác định rằng g(user:112, editor, workspace:111) là true.workspace cũng chứa trong workspace (
)
thông qua chính sách g2. Ta có thể xác định rằngg2(workspace, workspace) là true.delete không phù hợp với chính sách delete của vai trò editor trên không gian làm việc. Ta có thể xác định rằng delete == delete là true, nhưng vì không có chính sách nào cho phép editor xóa không gian làm việc, nên yêu cầu truy cập này bị từ chối.Tương tự, các yêu cầu truy cập khác cũng được đánh giá dựa trên vai trò của người dùng trong không gian làm việc và các chính sách đã định nghĩa. Kết quả của mỗi yêu cầu sẽ cho biết liệu yêu cầu đó có được phép hay không, cùng với lý do dựa trên chính sách nào đã cho phép hoặc từ chối yêu cầu đó. Chi tiết có thể xem tại website playground của Casbin cho ví dụ trên tại https://editor.casbin.org/#E7VKBXR2T.
Phụ lục C. HEALTH CHECK
note servicenote service
note service
note service
Phụ lục D. NX
Hình D.1 dưới đây thể hiện dependency graph của monorepo được sinh bởi Nx.
Phụ lục E. OBSERVABILITY
Hình E.1 dưới đây thể hiện log từ hệ thống được xem qua Grafana.