Bỏ qua, đến nội dung

FAQ - Kiến trúc

Các câu hỏi thường gặp khi đọc phần kiến trúc Digiforce.

Tổng quan & thiết kế

Tại sao chọn Koa thay vì Express?

Koa hỗ trợ async/await middleware native (onion model), phù hợp với pipeline phức tạp nhiều tầng của Digiforce (Koa app → data source → resource → action). Express callback-based sẽ khó quản lý thứ tự middleware và error propagation qua nhiều layer. Ngoài ra, Application kế thừa trực tiếp từ Koa class, giúp mixin thêm AsyncEmitter để vừa là HTTP server vừa là event bus.

"Metadata-driven" nghĩa là gì trong thực tế?

Thay vì hardcode model trong code rồi deploy, Digiforce cho phép:

  1. Admin tạo collection qua UI → metadata lưu vào DB → db.sync() tạo table thật.
  2. UI Schema lưu trong database → thay đổi giao diện không cần rebuild.
  3. Field type/interface khai báo runtime → plugin đăng ký field mới, có hiệu lực ngay.

Kết quả: thay đổi cấu trúc dữ liệu và giao diện mà không cần build code, không cần deploy lại.

Tại sao tách hai runtime (server + client) hoàn toàn?

  • Scalability: server có thể scale horizontal (nhiều instance) độc lập với client build.
  • Security: client không truy cập trực tiếp database hay repository - chỉ giao tiếp qua REST API.
  • Flexibility: client build thành static file, có thể serve qua CDN hoặc cùng server.
  • Plugin isolation: plugin server (data model, action) và plugin client (component, route) có lifecycle riêng, không ảnh hưởng lẫn nhau khi lỗi.

Middleware & pipeline

Tại sao dùng Toposort cho middleware thay vì thứ tự đăng ký?

Khi có nhiều plugin, mỗi plugin thêm middleware mà không biết plugin khác đã thêm gì. Nếu dùng thứ tự đăng ký (index-based), plugin load sau sẽ luôn chạy sau - không linh hoạt.

Toposort với tag, before, after cho phép:

  • Plugin A khai báo: "middleware của tôi chạy sau auth".
  • Plugin B khai báo: "middleware của tôi chạy trước acl".
  • Hệ thống tự tính thứ tự đúng bất kể thứ tự load.

Auth và ACL nằm ở tầng nào? Tại sao?

Auth và ACL nằm trong data source pipeline (tầng 2), không phải Koa app middleware (tầng 1).

Lý do: mỗi data source có thể có ACL riêng (ví dụ: data source ngoài có quy tắc phân quyền khác data source chính). Nếu ACL nằm ở tầng Koa, sẽ chỉ có một bộ quyền cho tất cả data source.

Tầng 1 (Koa): reqId → logger → bodyParser → cors → dataSource
Tầng 2 (Data source): auth → ResourceManager → ACL → pre-action → handler

Request đi qua bao nhiêu middleware trước khi đến handler?

Trung bình 10-15 middleware qua 2 tầng:

  • Tầng 1 (Koa): ~8 middleware (reqId, audit, logger, bodyParser, cors, i18n, dataWrapping, dataSource).
  • Tầng 2 (data source): ~6-7 middleware (validate-filter, auth, ResourceManager, ACL, parseVariables, dataTemplate, pre-action).

Plugin có thể thêm middleware ở cả hai tầng, nhưng nên hạn chế middleware global vì ảnh hưởng mọi request.


Data source & database

Data source "main" là gì? Có bắt buộc không?

. Data source main là data source mặc định, tạo bởi Application.createMainDataSource() trong init(). Nó chứa database chính (Sequelize) và được dùng khi request không có header x-data-source.

Mọi collection, resource, action mặc định đều thuộc data source main. Plugin và admin UI thao tác trên main trừ khi chỉ định khác.

Khi nào cần thêm data source ngoài?

  • Kết nối database legacy (ví dụ: SQL Server, Oracle) cùng lúc với DB chính.
  • Tích hợp hệ thống ngoài qua adapter (REST API → data source).
  • Multi-tenant: mỗi tenant có database riêng.

Thêm data source: dùng factory hoặc trực tiếp dataSourceManager.add(new MyDataSource(...)).

db.sync() có an toàn cho production không?

db.sync() so sánh metadata collection với table thực rồi ALTER (thêm column, tạo index) - không DROP trừ khi gọi db.clean({ drop: true }).

Tuy nhiên:

  • Production nên dùng migration (file migration trong plugin) thay vì sync tự động.
  • db.sync() phù hợp cho dev/test và khi install/upgrade.
  • Luôn backup trước khi upgrade trên production.

Plugin

Tại sao hai PluginManager riêng biệt (server vs client)?

Server PluginManager quản lý:

  • Collection, migration, resource, action, ACL - cần truy cập database, Koa context.

Client PluginManager quản lý:

  • Component, route, schema - chạy trong browser, không có DB.

Gộp chung sẽ buộc client phải phụ thuộc server code (hoặc ngược lại), phá vỡ tách biệt runtime.

Plugin load theo thứ tự nào?

  1. Preset plugins load trước (defined trong options.plugins khi tạo Application).
  2. Other plugins load sau, theo thứ tự dependency (package.json dependencies/peerDependencies).
  3. Trong cùng nhóm, thứ tự alphabetical trừ khi có dependency constraint.

WARNING

Plugin A define collection mà plugin B cần → A phải load trước B. Nếu dependency không khai báo rõ, extendCollection có thể bị delay và apply muộn.

Plugin lỗi có crash cả hệ thống không?

Tùy giai đoạn:

  • Lỗi trong load(): hệ thống bắt lỗi, log warning, nhưng có thể khiến feature không hoạt động.
  • Lỗi trong install(): install dừng giữa chừng, cần fix plugin rồi chạy lại.
  • Lỗi runtime (trong action handler): chỉ ảnh hưởng request đó, trả 500 cho client.
  • Lỗi client plugin: chỉ ảnh hưởng phần UI do plugin đó render.

Bảo mật

ACL engine vs plugin-acl - khi nào dùng cái nào?

ACL Engine (@digiforce-nc/acl)Plugin ACL (plugin-acl)
Khi nào dùngKiểm tra quyền runtime (can(), allow())Quản lý cấu hình quyền (UI admin, REST API)
Truy cậpapp.acl hoặc dataSource.aclEnable plugin, dùng qua UI hoặc API
Plugin tự viếtImport trực tiếp @digiforce-nc/aclThường không cần - dùng API acl.*

Nếu plugin chỉ cần check quyền hoặc đăng ký allow/snippet, dùng app.acl (engine). Nếu cần CRUD cấu hình quyền (tạo role mới, gán action), đó là trách nhiệm plugin-acl.

Fixed params có thể bị bypass từ client không?

Không. Fixed params được inject ở tầng ACL middleware, sau khi merge params từ request. Action.mergeParams() dùng chiến lược and-merge cho filter - client không thể ghi đè filter ACL.

Ví dụ: ACL inject { createdById: 1 }, client gửi { status: 'active' } → kết quả: { $and: [{ createdById: 1 }, { status: 'active' }] }.

Tại sao skipCheck cho phép bỏ qua xác thực?

skipCheck không phải lỗ hổng - nó được thiết kế cho:

  • Public endpoint đã đăng ký qua acl.allow(resource, action, 'public').
  • Hệ thống tắt ACL (app.options.acl = false) - chỉ dùng khi dev/test.
  • Internal call đánh dấu ctx.skipAuthCheck = true - code server tự gọi.

Nếu không có điều kiện nào trên, skipCheck() trả false → request bắt buộc qua auth.check().


Vận hành & debug

Multi-instance cần cấu hình gì?

  • Redis bắt buộc - cho PubSubManager, SyncMessageManager, CacheManager, LockManager.
  • Sticky session cho WebSocket (nếu dùng load balancer).
  • Object storage dùng chung (S3/MinIO) thay vì local disk.
  • Cùng DB - tất cả instance kết nối cùng PostgreSQL/MySQL.

Upgrade fail giữa chừng thì sao?

  • Migration có thể đã chạy một phần. Hệ thống track version qua ApplicationVersion.
  • Chạy lại app upgrade sẽ skip migration đã hoàn thành (Umzug tracking).
  • Nếu migration lỗi logic (data corrupt), cần rollback DB từ backup rồi fix migration code trước khi chạy lại.

Làm sao biết request đi qua middleware nào?

  1. Request ID (generateReqId) - tất cả log đều kèm request ID.
  2. Logger middleware - ghi timestamp, method, URL, status, duration.
  3. SQL logger - ghi query thực tế (enable trong config database).
  4. Telemetry - hook vào lifecycle và request pipeline nếu enable.

Bật SQL log trong development: set DB_LOGGING=true hoặc cấu hình logger trong database options.


Đọc tiếp