从插件系统到微内核平台:一篇从入门到进阶的 NocoBase 架构笔记
如果你第一次听到“微内核架构”,可以先把它理解成一句话:
1 | 核心系统只保留最稳定、最通用的能力,具体业务能力都通过插件接进来。 |
这和我们平时用的很多软件很像。
浏览器本身只提供浏览网页、管理标签页、运行扩展的基础能力。广告拦截、翻译、密码管理、截图工具,都是扩展插件提供的。
VS Code 本身是一个编辑器内核。Python、Go、Java、Markdown、GitLens、Docker、数据库管理,这些能力大多来自插件。
平台型后端也可以用类似思路设计。核心不直接写死 CRM、审批、报表、文件、工作流、AI 调用,而是提供一套稳定的注册和运行机制,让这些能力以插件形式接入。
NocoBase 是一个很好的参考对象。它采用微内核架构,核心负责插件生命周期、依赖管理和基础能力封装,业务功能由插件提供。服务端插件可以注册数据表、资源接口、权限、事件、定时任务、日志、Telemetry 和迁移;客户端插件可以注册路由、页面、区块、字段和按钮。
本文不是复刻 NocoBase 源码,也不是贴一大段 Python 教学代码,而是把它背后的架构思路讲成一个从入门到进阶的路径。
读完这篇,你应该能回答三个问题:
1 | 1. 插件系统和微内核到底解决什么问题? |
先看问题
假设我们一开始写的是一个普通业务系统。需求来了,我们就在代码里加功能:
1 | 新增客户表 |
早期这样写没有问题,因为系统小,业务也清楚。
但当它变成一个平台以后,情况会变复杂:
1 | 客户模块可能要被替换; |
这时继续往核心系统里塞业务代码,核心会越来越大,也越来越难改。
微内核架构的目标就是把这个问题反过来:
1 | 核心不要知道太多业务。 |
所以,微内核不是为了“少写代码”,而是为了让系统长期演进时不失控。
入门:最小插件系统长什么样
最小的插件系统可以很简单:
1 | Kernel |
其中:
1 | PluginManager 负责加载、启用、禁用插件 |
一个非常简化的插件可能长这样:
1 | class ArticlePlugin: |
应用启动时:
1 | app.plugins.add(ArticlePlugin()) |
这已经具备插件系统的雏形:
1 | 核心不直接写 article.publish; |
这一步适合入门理解,但还不是完整平台。
入门:为什么只有 PluginManager 不够
如果插件只负责注册几个函数,PluginManager 就够了。
但真实平台里的插件通常要做更多事情:
1 | 定义数据表; |
如果没有统一机制,每个插件就会自己想办法:
1 | 插件 A 自己建表; |
最后的问题是:核心看起来变小了,但系统并没有变简单,只是复杂度被分散到了插件里。
完整微内核需要的不只是“能加载插件”,而是“所有重要能力都有受控入口”。
进阶:平台需要哪些注册表
可以把一个平台内核想象成一组注册表。
插件不能随便改系统内部状态,而是通过这些注册表把能力挂进来。
| 注册表 | 解决的问题 | 插件通常注册什么 |
|---|---|---|
| PluginManager | 插件怎么加载、排序、启停 | 插件本身、依赖关系、生命周期 |
| CollectionManager | 数据模型怎么扩展 | 表、字段、关联、默认值、校验 |
| ResourceManager | API 能力怎么暴露 | resource:action、CRUD、自定义操作 |
| ACL | 权限怎么统一判断 | 角色权限、公开接口、数据范围 |
| EventBus | 副作用怎么解耦 | 事件监听器、数据变化监听器 |
| CronJobManager | 后台任务怎么托管 | 定时任务、清理任务、同步任务 |
| FrontendRegistry | 前端怎么扩展 | 路由、页面、区块、字段、按钮 |
| MigrationManager | 版本升级怎么处理 | 表结构变更、数据迁移、配置迁移 |
| Logger / Telemetry | 插件怎么被观测 | 日志、指标、链路追踪 |
这张表是理解微内核平台的关键。
一个插件不应该只是“一段被 import 的代码”。它更像一个完整扩展包:
1 | Plugin = 后端能力 + 前端能力 + 数据模型 + 权限 + 配置 + 生命周期 + 迁移 |
NocoBase 的插件体系就是这个方向:不是只做后端模块,而是把服务端和客户端能力都纳入插件化运行时。
进阶:一条请求如何串起来
先看一个具体例子。
假设有一个 ArticlePlugin,它提供文章管理能力。它做了几件事:
1 | 1. 注册 articles 数据模型 |
用户点击“发布文章”按钮后,请求可能是:
1 | /api/articles:publish |
它在平台里的路径是:
1 | 前端按钮 |
这条链路里,文章业务只是一个插件。权限、事件、审计、指标、错误处理都不应该散落在 ArticlePlugin 里,而应该经过平台统一入口。
这就是微内核平台和普通业务系统的差别。
普通业务系统关心:
1 | 怎么写一个发布文章接口? |
平台系统关心:
1 | 插件如何声明 articles:publish? |
问题一变,架构就变了。
进阶:Collection 先行
平台型系统里,数据模型应该先于页面和接口。
NocoBase 里有一个核心概念叫 Collection。你可以把它理解成“带元信息的数据表定义”。
它不只是表名,还包括:
1 | 字段名 |
比如文章插件可以定义:
1 | app.collections.define( |
这个定义不是只给数据库看的。它后面还可以推导出很多东西:
1 | CRUD API |
很多系统会反过来做:
1 | 先写页面; |
平台系统更稳的顺序是:
1 | 先定义 Collection |
这就是“数据模型驱动”。
如果平台支持动态建模,还要注意 SQL 安全。值可以参数绑定,但表名、字段名不能作为普通参数传入,必须白名单校验:
1 | IDENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") |
动态建模越强,越要把字段名、排序、过滤、关联查询这些边界做清楚。
进阶:Resource Action 统一 API
如果每个插件都随便写路由,系统很快会变乱:
1 | /api/article/create |
命名不统一,权限不统一,审计不统一,日志和指标也不统一。
NocoBase 风格的 ResourceManager 更推荐把能力抽象成:
1 | resource:action |
例如:
1 | articles:list |
这样 CRUD、图表聚合、审批提交、文件上传、导入导出,本质上都可以走同一种调用协议。
插件注册自定义 action:
1 | app.resources.register_action( |
调用时:
1 | app.resources.call(ctx, "articles:publish", id=1) |
ResourceManager 负责统一做这些事:
1 | 解析 resource/action |
这就是平台化的价值。业务插件只关心“我要提供什么 action”,治理逻辑由统一入口处理。
进阶:ACL 不只是隐藏按钮
很多系统把权限理解成“前端按钮显不显示”。
这只是体验,不是安全。
真正的权限必须在后端统一执行,而且最好能进入查询层。
最基本的权限可以这样描述:
1 | app.acl.grant("admin", "articles", "*") |
含义是:
1 | admin 可以操作 articles 的全部 action; |
ResourceManager 在执行 action 前统一检查:
1 | if not app.acl.can(ctx, resource, action): |
进阶一点,还要支持数据范围。
比如销售只能看自己的订单:
1 | orders:list |
请求链路应该是:
1 | ResourceManager |
所以权限不应该散落在每个业务函数里,也不应该只停留在 UI 层。它应该绑定到 resource:action,并在统一入口执行。
进阶:事件用于解耦副作用
事件适合处理副作用。
比如:
1 | 记录审计日志 |
文章发布时,ArticlePlugin 不应该手动调用所有插件:
1 | audit.record(...) |
这样会让 ArticlePlugin 知道太多其他插件。
更好的方式是发事件:
1 | app.events.emit("articles.published", article=article) |
其他插件自己监听:
1 | app.events.on("articles.published", audit_listener) |
事件通常分两类:
1 | app.events 应用级事件:启动、停止、插件加载、资源调用 |
事件适合副作用,但不适合承载主流程里的强依赖逻辑。
如果“发布文章必须先扣库存”这种逻辑是主流程的一部分,就应该显式调用,不应该藏在事件监听器里。事件监听器失败以后要不要回滚主流程,也必须提前定义清楚。
生产系统里,事件监听还要能注销:
1 | handle = app.events.on("articles.published", listener) |
否则插件禁用以后监听器还在运行,就是资源泄漏。
进阶:后台任务要由内核托管
插件不要自己偷偷开线程、定时器或消费者。
错误示例:
1 | threading.Thread(target=self.loop).start() |
这样做的问题是:
1 | 内核不知道任务存在; |
更好的方式是通过 CronJobManager:
1 | app.cron.add_job( |
禁用插件时:
1 | app.cron.remove_job("article.daily_report") |
生产里的任务管理还要考虑:
1 | 分布式锁 |
任务托管的核心不是“帮你跑 cron”,而是让后台行为纳入平台治理。
进阶:前端也要插件化
很多团队一开始只做后端插件。后来发现菜单、页面、字段、表单、按钮、设置页也都要扩展,于是前端到处打补丁。
平台系统更合理的设计是:插件是全栈扩展单元。
1 | Plugin = Backend Extension + Frontend Extension + Metadata + Lifecycle + Permissions + Configuration |
后端插件注册:
1 | 数据模型 |
前端插件注册:
1 | 路由 |
一个插件目录可以长这样:
1 | article-plugin/ |
这样,ArticlePlugin 不只是“后端多一个接口”,而是可以完整带来文章表、文章接口、文章权限、文章页面、文章表单、文章按钮和文章设置页。
这才是平台插件。
高阶:生命周期不能太粗
入门版插件系统可能只有:
1 | activate() |
真实平台不够用。
参考 NocoBase 的插件生命周期,一个更完整的拆分是:
1 | static_import |
可以用更通俗的话理解:
| 阶段 | 什么时候发生 | 适合做什么 |
|---|---|---|
| static_import | 类级初始化 | 注册全局类型、加载静态描述 |
| after_add | 插件加入管理器后 | 准备轻量状态、读取基本配置 |
| before_load | 所有插件 load 前 | 定义 Collection、注册中间件、监听事件 |
| load | 插件加载时 | 注册资源、API、权限、前端扩展 |
| install | 第一次启用时 | 初始化数据、默认配置、默认权限 |
| after_enable | 每次启用后 | 启动任务、建立连接、预热缓存 |
| after_disable | 禁用时 | 停止任务、关闭连接、注销监听 |
| remove | 删除时 | 清理插件私有资源 |
这里最容易混淆的是 install 和 migration。
install() 只处理首次启用。插件升级以后要改表结构、修数据、补配置,应该走 MigrationManager,而不是重复执行 install。
另一个关键点是 before_load 和 load 的分工。
通常:
1 | before_load 先注册模型和底层准备工作 |
这样可以减少“插件 A 的 API 依赖插件 B 的 Collection,但 B 还没注册”的问题。
高阶:四个平面看平台内核
到这里,我们可以把微内核平台分成四个平面。
第一是 Runtime Plane,负责插件怎么运行:
1 | PluginManager |
它处理插件发现、依赖排序、启用、禁用、卸载、状态持久化和版本约束。
第二是 Extension Plane,负责插件怎么提供业务能力:
1 | CollectionManager |
它决定插件可以向系统注册哪些能力。
第三是 Governance Plane,负责插件怎么被治理:
1 | ACL |
插件越多,治理越重要。没有统一治理面,平台只能依赖插件自觉。
第四是 Configuration Plane,负责插件配置、开关、密钥和升级:
1 | ConfigManager |
这四个平面合起来,才是平台内核,而不是一个孤立的 PluginManager。
高阶:日志和 Telemetry 是一等公民
平台内核不能只关心插件怎么运行,还要关心插件怎么被观察。
日志至少要带上:
1 | plugin_name |
指标至少应该覆盖:
1 | plugin_enable_total |
Trace 解决的是链路问题。一次请求可能经过:
1 | API request |
没有 trace_id,这些日志会散在各处。
NocoBase 的 Telemetry 模块基于 OpenTelemetry 思路提供 Trace 和 Metric 能力。自己实现平台时,即使先做简化版,也应该优先覆盖这些统一入口:
1 | ResourceManager |
因为统一入口观测一次,比每个插件各写一遍更可靠。
高阶:从教学版到生产版还差什么
如果只是学习,一个 Python demo 可以很小:
1 | Application |
这足够讲清楚概念。
但生产系统还要补很多工程能力。
第一,插件 manifest。
插件元信息不要只写在类里,应该有独立描述:
1 | name: article |
第二,插件发现机制。
教学版可以手动注册:
1 | app.pm.add(ArticlePlugin) |
生产版要支持本地插件目录、包管理、远程插件市场或语言生态里的 entry points。
第三,版本约束。
依赖不只是插件名,还包括插件版本、内核 API 版本、语言版本、数据库版本和前端运行时版本。
第四,MigrationManager。
字段新增、字段重命名、类型变更、索引调整、配置迁移、数据补偿都应该走 migration,并记录每个插件每个 migration 是否执行过。
第五,事务边界。
文章发布成功后刷新搜索索引失败,要不要回滚发布状态?订单创建后工作流失败,要不要回滚订单?通知失败是否影响主流程?这些不能靠事件机制自动回答,必须由平台语义定义。
第六,禁用和删除。
禁用插件时要撤销事件监听、中间件、缓存、连接、消费者、定时任务、前端路由、权限片段和临时资源。删除插件时是否删除用户数据,更要谨慎。
第七,第三方插件隔离。
如果插件来自第三方或不可信,不建议直接运行在同一个进程里。进程内插件很难安全限制文件访问、网络访问、CPU、内存和死循环。更稳妥的是独立进程、容器、Worker Sandbox、HTTP/gRPC/IPC 调用、资源配额和签名校验。
第八,完整可观测性。
生产版应该把 Trace、Metric、Span、Exporter 和 Context propagation 做完整,至少覆盖资源调用、数据库操作、插件生命周期和后台任务。
常见误区
第一个误区是把插件系统等同于动态 import。
1 | 插件系统 = importlib.import_module(plugin_name) |
这只是加载方式,不是架构。真正的插件系统必须有协议、生命周期、依赖管理、能力注册、权限控制、错误治理、日志监控和资源清理。
第二个误区是 Kernel 写太多业务逻辑。
如果 Kernel 里出现很多业务方法,边界通常已经坏了:
1 | class Kernel: |
这些应该属于插件,而不是核心。
第三个误区是所有扩展都用 Hook。
Hook 适合事件通知,不适合表达所有扩展。
| 场景 | 推荐机制 |
|---|---|
| 多个插件监听某个事件 | Event / Hook |
| 插件提供一个可调用能力 | Service / Resource |
| 插件注册 API 操作 | ResourceManager |
| 插件定义数据结构 | CollectionManager |
| 插件控制权限 | ACL |
| 插件做后台任务 | CronJobManager |
第四个误区是权限散落在业务代码里。
权限应该统一挂在 resource:action 上,并进入后端查询层。
第五个误区是插件自己开线程、开消费者、开定时器。
后台任务必须由内核托管。
第六个误区是混用 install 和 migration。
首次安装和版本升级是两件事。
第七个误区是禁用插件但不清理资源。
插件禁用后还在监听事件、还在跑任务、还占用连接,就是资源泄漏。
从入门到进阶的落地路径
如果你想自己实现一个学习版,可以按四步走。
第一步,只做插件加载。
目标是理解:
1 | 什么是 Kernel |
这一步可以只有:
1 | Application |
第二步,引入数据和 API 注册。
目标是理解:
1 | 插件如何定义 Collection |
这一步可以加入:
1 | CollectionManager |
第三步,引入事件、任务和观测。
目标是理解:
1 | 主流程和副作用如何拆开 |
这一步加入:
1 | EventBus |
第四步,走向平台化。
目标是理解:
1 | 前端如何插件化 |
这一步加入:
1 | FrontendRegistry |
不要一上来就写完整平台。先把核心链路跑通,再逐步补治理能力。
架构检查清单
设计自己的微内核平台时,可以用下面这份清单自检。
Kernel 边界:
1 | [ ] Kernel 是否只保留稳定机制? |
插件生命周期:
1 | [ ] 是否区分首次安装和每次启用? |
数据模型:
1 | [ ] 插件是否可以定义 Collection? |
API 与权限:
1 | [ ] API 是否统一进入 ResourceManager? |
事件与任务:
1 | [ ] 是否有应用级事件? |
可观测性:
1 | [ ] 插件生命周期是否有日志? |
安全:
1 | [ ] 第三方插件是否隔离运行? |
总结
入门看,微内核就是:
1 | 核心少写业务,业务通过插件接入。 |
进阶看,微内核平台不是只有 PluginManager,而是一组稳定注册表:
1 | PluginManager |
高阶看,微内核真正解决的是长期演进问题:
1 | 插件如何安装、启用、禁用、删除; |
从 NocoBase 可以学到的核心不是某个具体 API,而是一套平台化纪律:
1 | 核心保持小而稳定; |
所以,微内核不是插件加载器。
它更像一个平台操作系统:核心负责提供稳定规则,插件负责带来变化。规则越清晰,平台越能在长期演进中保持可控。
参考资料
- 标题: 从插件系统到微内核平台:一篇从入门到进阶的 NocoBase 架构笔记
- 作者: Jiayu Xu
- 创建于 : 2026-05-20 16:00:13
- 更新于 : 2026-05-20 16:10:45
- 链接: https://www.rasior.com/2026/05/20/20260520_microkernel-platform-architecture/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。