Giao diện
Plugins & extension
Trang này mô tả mô hình mở rộng của Digiforce: mọi tính năng nghiệp vụ, tích hợp hay workflow lớn đều đi qua Plugin trên server và/hoặc client, thay vì sửa trực tiếp packages/core. Chi tiết từng gói: Handbook plugin và tài liệu trong packages/.
1) Vai trò của Plugin trong kiến trúc
| Mục tiêu | Giải thích ngắn |
|---|---|
| Mở rộng không fork core | Feature ship dưới dạng package plugin; Application (server/client) chỉ load và gắn extension vào các manager có sẵn. |
| Metadata-driven | Nhiều hành vi (collection, field, UI schema, action) được khai báo và đăng ký runtime thay vì hardcode trong core. |
| Phân tách trách nhiệm | Core lo pipeline HTTP, data source, repository, ACL engine; plugin lo nghiệp vụ, UI block, tích hợp ngoài. |
Plugin (server + client tùy gói) có thể chạm tới (không giới hạn cứng, nhưng phải tuân contract):
- Collection / Field / relation / migration (server).
- Resource / Action / pre-action / handler tùy chỉnh (server).
- ACL: snippet, policy,
availableActions, fixed params (server, thường qua plugin nhưplugin-acl). - UI: component map, route, Schema (initializer / settings), menu, provider (client).
- Workflow, notification, file, integration - theo từng plugin cụ thể.
2) Hai nhánh runtime: Server Plugin vs Client Plugin
Digiforce có hai PluginManager độc lập (không gộp một class):
- Server: gắn với
Application(Koa), load migration, đăng ký resource/action, hook vào DB/ACL. - Client: gắn với SPA React, đăng ký route, component, schema extension, remote plugin list.
Đọc sâu: Server - Application, Client - Application.
3) Plugin thường chạm vào đâu - server và client
Hai bảng dưới là ví dụ phổ biến (data model, API, UI…), không liệt kê hết mọi API. Chi tiết từng gói xem Handbook plugin.
Server
| Khu vực | Ví dụ việc plugin làm |
|---|---|
| Data model | Định nghĩa collection, field, migration; sync schema. |
| Resource / Action | resourceManager.define, đăng ký action handler, pre-action. |
| DataSource | Hook beforeAddDataSource / afterAddDataSource (ví dụ gắn availableActions vào ACL). |
| ACL | acl.allow, registerSnippet, policy theo resource/action. |
| Lifecycle | load, install, upgrade, enable / disable - xem mục 4. |
Client
| Khu vực | Ví dụ việc plugin làm |
|---|---|
| Component | app.addComponents / map component cho UI Schema. |
| Route | router.add - route tree dạng dot path. |
| Schema | SchemaSettingsManager, SchemaInitializerManager, menu block. |
| Settings | PluginSettingsManager - màn cấu hình plugin. |
| Remote plugin | Danh sách enabled từ API (ví dụ pm:listEnabled) rồi dynamic load. |
4) Lifecycle - server (PluginManager + Application)
4.1 Runtime load (Application.load → PluginManager.load)
Application.load(options) luôn gọi pm.initPlugins() rồi pm.load(options).
Diễn giải:
Application.load(options):- Tạo worker id, init telemetry, gọi
pm.initPlugins()rồipm.load(options); nếuoptions.syncthìdb.sync(), nếuhooks !== falsethì emitbeforeLoad/afterLoad.
- Tạo worker id, init telemetry, gọi
pm.initPlugins(): chuẩn bị danh sách plugin instance (preset + cấu hình) trước khi thực sự load.PluginManager.load(options)- Vòng 1: từng plugin enabled →
plugin.beforeLoad(). - Vòng 2: từng plugin enabled + chưa
state.loaded→emitAsync("beforeLoadPlugin")→plugin.loadCollections()→plugin.loadAI()→plugin.load()→ setstate.loaded = true→emitAsync("afterLoadPlugin").
- Vòng 1: từng plugin enabled →
- Ý nghĩa:
- Hai vòng giúp tách bước “chuẩn bị” (
beforeLoad) khỏi bước “gắn model/resource/action” (các hàmload*), dễ debug/log theo từng plugin. state.loadedđảm bảo plugin không bị load lại nhiều lần trong cùng process.
- Hai vòng giúp tách bước “chuẩn bị” (
Gợi ý quan sát:
- Plugin đã enabled nhưng không thấy effect sau khi start: xem log
beforeLoadPlugin/afterLoadPluginhoặc tracePluginManager.loadđể chắc chắn plugin thật sự được load. - Crash lúc start: thường lỗi nằm trong
loadCollections()(model/migration sai) hoặcload()(đăng ký resource/action lỗi).
4.2 Install flow (Application.install + pm.install)
Diễn giải:
Application.install(options)- (tùy chọn)
db.clean({ drop: true })khiclean/force. - Nếu đã có dấu hiệu
isInstalled()thì chỉ log cảnh báo và return. - Ngược lại:
reInit()→db.sync()→load({ hooks: false })→emitAsync("beforeInstall")→pm.install()→version.update()→emitAsync("afterInstall")→ (nếu app đang chạy)restart().
- (tùy chọn)
PluginManager.install(options)- Gọi
app.db.sync()một lần nữa để chắc schema DB đã khớp với model plugin. - Lặp qua plugin chưa
installed: emitbeforeInstallPlugin→ gọiplugin.install(options)→ set cờinstalled(cả state lẫn DB) → emitafterInstallPlugin.
- Gọi
Gợi ý quan sát:
- Cài xong nhưng thiếu seed/config mặc định của plugin: kiểm tra log
beforeInstallPlugin/afterInstallPluginvà xem plugin có overrideinstall()không.
4.3 Enable flow (pm.enable)
Diễn giải:
PluginManager.enable:sort(pluginNames)để tôn trọng dependency giữa các plugin.- Với plugin chưa có instance:
addOrThrow(pluginName)trước khi bật. - Với plugin đang disabled:
- Bước “load lại runtime”:
beforeLoad()→loadCollections()→loadAI()→load(). - Bước enable: emit
beforeEnablePlugin→plugin.beforeEnable()→ emitafterEnablePlugin.
- Bước “load lại runtime”:
- Sau đó:
db.sync()nếu cần, chạyplugin.install()cho plugin chưa installed, rồirepository.updateOrCreate(...)để lưu trạng thái enabled/installed/version. - Cuối cùng: có thể gọi
app.tryReloadOrRestart({ recover: true })khi có lỗi/đổi trạng thái nhiều plugin.
Gợi ý quan sát:
- Enable theo
*nhưng chỉ một số plugin bị lỗi: log sẽ xuất hiện trongbeforeEnablePlugin/beforeEnable, còn các plugin khác vẫn bật thành công.
upgrade có nhiều pha migration xen giữa preset/other plugins; xem chi tiết Server - Application phần upgrade().
5) Lifecycle - client (PluginManager + SPA)
5.1 Init (PluginManager.init)
Diễn giải:
- Static plugin: plugin bundle sẵn, truyền vào
_pluginskhi tạoPluginManager; luôn đi quainitStaticPlugins→add→afterAddtrước. - Remote plugin (khi
loadRemotePlugins === true):- Client gọi
pm:listEnabledquaapiClient - Sau đó dùng
getPluginsđể tải bundle và resolve class. - Mỗi plugin remote cũng đi qua
add(pluginClass, info)→afterAdd().
- Client gọi
- Mọi plugin (static + remote) đều phải init xong ở đây trước khi bước
pm.load()chạy (mục 5.2).
Gợi ý quan sát:
- Plugin remote không xuất hiện: kiểm tra response
pm:listEnabled, network error khi tải bundle, hoặc lỗi tronggetPlugins(...)/add(...)(xem filePluginManager.tsclient).
5.2 Runtime load (Application.load + pm.load)
Diễn giải:
Application.load():loadWebSocket()trước, sau đópm.load(), cuối cùnguiCore.uiConfig.load().- Bất kỳ lỗi trong
pm.loadhoặcuiConfig.loadđều được bắt và chuyển thànhLOAD_ERRORhiển thị trong UI.
PluginManager.load()(client):await this.initPluginsđảm bảo cả static + remote plugin đã được add.- Vòng 1: mỗi plugin:
beforeLoad()- chuẩn bị context/config. - Vòng 2: mỗi plugin:
load()→ dispatchplugin:<name>:loadedquaeventBus.
- Ảnh hưởng lên UI:
- Các component/route/schema mà plugin thêm sẽ chỉ có sau
load(). uiCore.uiConfig.load()đọc lại config UI sau khi plugin đã đăng ký đủ extension points.
- Các component/route/schema mà plugin thêm sẽ chỉ có sau
Gợi ý quan sát:
- Mất component hoặc route sau khi bật plugin client: kiểm tra event
plugin:<name>:loadedvà thứ tựpm.loadvsuiConfig.loadtrong log.
6) Plugin có thể mở rộng ở đâu (các chỗ hay dùng)
Phần này liệt kê các “chỗ” mà plugin thường thêm mới hoặc thay đổi hành vi (API, bảo mật, UI).
Runtime / API
| Nơi mở rộng | Dùng để làm gì |
|---|---|
resourceManager.define(...) | Đăng ký resource và action tùy chỉnh. |
registerActionHandler / handler tương đương | Gắn body xử lý cho action. |
registerPreActionHandler | Chuẩn hóa / validate trước handler chính (filter, template, biến). |
dataSourceManager.beforeAddDataSource / afterAddDataSource | Mở rộng khi thêm data source (ví dụ sync ACL). |
Security
| Nơi mở rộng | Dùng để làm gì |
|---|---|
acl.allow(...) | Cho phép resource/action theo điều kiện. |
acl.registerSnippet | Snippet động (policy phức tạp). |
acl.setAvailableAction | Gắn action có sẵn cho ngữ cảnh collection/role. |
| Auth | Middleware auth, authenticator tùy loại (tuỳ plugin auth). |
UI (client)
| Nơi mở rộng | Dùng để làm gì |
|---|---|
app.addComponents(...) | Đăng ký component cho UI Schema. |
app.router.add(...) | Thêm route SPA. |
schemaSettingsManager.addItem(...) | Mục schema settings. |
schemaInitializerManager.add(...) | Mục schema initializer (thêm block/action vào UI builder). |
pluginSettingsManager.add(...) | Màn cài đặt plugin trong admin. |
Danh sách trên là điểm vào phổ biến; plugin cụ thể có thể dùng thêm API từ @digiforce-nc/client / server - tra README từng package.
7) Dependency và thứ tự load
- Thứ tự plugin ảnh hưởng trực tiếp runtime: plugin định nghĩa Collection / Resource cần được load trước plugin chỉ “tiêu thụ” metadata đó.
- Plugin ACL, auth, data model nền thường cần ở nhóm sớm (theo dependency đã khai báo hoặc theo preset).
- Khai báo rõ ràng:
- dependency kỹ thuật:
dependencies/peerDependenciestrongpackage.json; - dependency nghiệp vụ: ghi trong docs plugin (
index.md,02-api-reference.md) để team và vận hành biết thứ tự bật plugin.
- dependency kỹ thuật:
- Guard: trong
beforeLoad/beforeEnablecó thể kiểm tra plugin bắt buộc đã tồn tại hoặc version tối thiểu, rồi fail fast với thông báo rõ.
8) Quy tắc an toàn khi mở rộng
- Mọi action ghi dữ liệu nên đi qua ACL phù hợp; tránh endpoint “public” trừ khi thiết kế rõ (read-only, form công khai, v.v.).
- Pre-action + validate schema trước khi vào Repository; ưu tiên transaction khi ghi nhiều bảng.
- Ưu tiên middleware / hook theo topo tại data source hoặc resource thay vì sửa global core khi có thể.
- Ghi log / telemetry cho luồng tích hợp để dễ truy vết khi plugin lỗi.