Bỏ qua, đến nội dung

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êuGiải thích ngắn
Mở rộng không fork coreFeature ship dưới dạng package plugin; Application (server/client) chỉ loadgắn extension vào các manager có sẵn.
Metadata-drivenNhiề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ệmCore 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ựcVí dụ việc plugin làm
Data modelĐịnh nghĩa collection, field, migration; sync schema.
Resource / ActionresourceManager.define, đăng ký action handler, pre-action.
DataSourceHook beforeAddDataSource / afterAddDataSource (ví dụ gắn availableActions vào ACL).
ACLacl.allow, registerSnippet, policy theo resource/action.
Lifecycleload, install, upgrade, enable / disable - xem mục 4.

Client

Khu vựcVí dụ việc plugin làm
Componentapp.addComponents / map component cho UI Schema.
Routerouter.add - route tree dạng dot path.
SchemaSchemaSettingsManager, SchemaInitializerManager, menu block.
SettingsPluginSettingsManager - màn cấu hình plugin.
Remote pluginDanh sách enabled từ API (ví dụ pm:listEnabled) rồi dynamic load.

4) Lifecycle - server (PluginManager + Application)

4.1 Runtime load (Application.loadPluginManager.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ồi pm.load(options); nếu options.sync thì db.sync(), nếu hooks !== false thì emit beforeLoad/afterLoad.
  • 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.loadedemitAsync("beforeLoadPlugin")plugin.loadCollections()plugin.loadAI()plugin.load() → set state.loaded = trueemitAsync("afterLoadPlugin").
  • Ý 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àm load*), 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.

Gợi ý quan sát:

  • Plugin đã enabled nhưng không thấy effect sau khi start: xem log beforeLoadPlugin/afterLoadPlugin hoặc trace PluginManager.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ặc load() (đă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 }) khi clean/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().
  • 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: emit beforeInstallPlugin → gọi plugin.install(options) → set cờ installed (cả state lẫn DB) → emit afterInstallPlugin.

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/afterInstallPlugin và xem plugin có override install() 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 beforeEnablePluginplugin.beforeEnable() → emit afterEnablePlugin.
    • Sau đó: db.sync() nếu cần, chạy plugin.install() cho plugin chưa installed, rồi repository.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 trong beforeEnablePlugin/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 _plugins khi tạo PluginManager; luôn đi qua initStaticPluginsaddafterAdd trước.
  • Remote plugin (khi loadRemotePlugins === true):
    • Client gọi pm:listEnabled qua apiClient
    • Sau đó dùng getPlugins để tải bundle và resolve class.
    • Mỗi plugin remote cũng đi qua add(pluginClass, info)afterAdd().
  • 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 trong getPlugins(...)/add(...) (xem file PluginManager.ts client).

5.2 Runtime load (Application.load + pm.load)

Diễn giải:

  • Application.load():
    • loadWebSocket() trước, sau đó pm.load(), cuối cùng uiCore.uiConfig.load().
    • Bất kỳ lỗi trong pm.load hoặc uiConfig.load đều được bắt và chuyển thành LOAD_ERROR hiể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() → dispatch plugin:<name>:loaded qua eventBus.
  • Ả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.

Gợi ý quan sát:

  • Mất component hoặc route sau khi bật plugin client: kiểm tra event plugin:<name>:loaded và thứ tự pm.load vs uiConfig.load trong 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ộngDùng để làm gì
resourceManager.define(...)Đăng ký resourceaction tùy chỉnh.
registerActionHandler / handler tương đươngGắn body xử lý cho action.
registerPreActionHandlerChuẩn hóa / validate trước handler chính (filter, template, biến).
dataSourceManager.beforeAddDataSource / afterAddDataSourceMở rộng khi thêm data source (ví dụ sync ACL).

Security

Nơi mở rộngDùng để làm gì
acl.allow(...)Cho phép resource/action theo điều kiện.
acl.registerSnippetSnippet động (policy phức tạp).
acl.setAvailableActionGắn action có sẵn cho ngữ cảnh collection/role.
AuthMiddleware auth, authenticator tùy loại (tuỳ plugin auth).

UI (client)

Nơi mở rộngDù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 / peerDependencies trong package.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.
  • Guard: trong beforeLoad / beforeEnable có 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.

Đọc tiếp